反应:如何使输入仅与提供的文本量一样宽?

Posted

技术标签:

【中文标题】反应:如何使输入仅与提供的文本量一样宽?【英文标题】:React: how to make an input only as wide as the amount of text provided? 【发布时间】:2021-01-13 11:38:17 【问题描述】:

简单的问题: 我正在尝试创建与提供给它们的文本一样大的输入。

沙盒:https://codesandbox.io/s/long-snowflake-6u13n?file=/src/Test.jsx

我的设计意图是动态生成输入,然后允许用户具有特定于每个输入的样式,这些样式在视觉上有助于根据外部事件分解每个句子。但在我继续前进之前,我的输入容器与其中的文本一样大非常重要。

为什么不使用文本区域? -- 我有特定于每个句子的数据,我想为其创建独特的样式。

有什么想法吗?

【问题讨论】:

【参考方案1】:

如果字体是等宽字体,则使用ch 单位会起作用,否则字符宽度会发生变化。我会通过渲染一个包含相同文本的内联元素来解决这个问题,每次输入字段值发生变化时测量它并立即隐藏它。

为此,最好创建一个单独的组件来渲染句子,我们称之为Sentence

const Test = () => 
  return (
    <div className="App">
      value.map(( sentence , i) => 
        return (
          <Sentence
            initialValue=sentence
            key=i
          />
        );
      )
    </div>
  );
;

Test 将传递初始值,然后Sentence 将继续保持自己的状态:

const Sentence = ( initialValue ) => 
  const [value, setValue] = React.useState(initialValue)

  return (
    <SentenceInput
      type="text"
      value=value
      onChange=(event) => 
        setValue(event.target.value)
      
    />
  )

接下来,我将添加一个span 元素,该元素将用作测量器元素,其中文本的样式应与输入元素中的样式相同,因此测量结果准确。在 Chrome 中的示例中,这意味着将字体大小设置为 13.3333px

现在最棘手的部分是,我们需要结合useEffectuseLayoutEffectuseEffect 将使测量器可见,然后useLayoutEffect 将测量并隐藏它

这是结果:

const Sentence = ( initialValue ) => 
  const [value, setValue] = React.useState(initialValue)
  const [visible, setVisible] = React.useState(false)
  const [width, setWidth] = React.useState('auto')
  const measurer = React.useRef(null)

  React.useEffect(() => 
    setVisible(true)
  , [value])

  React.useLayoutEffect(() => 
    if (visible) 
      const rect = measurer.current.getBoundingClientRect()
      setWidth(rect.width)
      setVisible(false)
    
  , [visible])

  return (
    <>
      <span
        ref=measurer
        style= fontSize: '13.3333px' 
      >
        visible && value
      </span>
      <SentenceInput
        type="text"
        value=value
        style= width: width + 1 
        onChange=(event) => 
          setValue(event.target.value)
        
      />
    </>
  )

我将1px 添加到计算的width 中,因为它似乎删除了输入字段中的一个小的水平滚动。

这是为了让您以您想要的方式进一步调整,例如当它达到视口宽度时应该如何表现。

【讨论】:

我做了一个简单的 html/CSS/JS 答案,你可以使用任何你认为对你的答案有用的东西。我稍后会删除我的(主要是关于将跨度/输入设置为相同大小的 CSS 部分)。 你为什么要删除你的?这是一种不同的,显然更优雅的方法。 ? 它没有反应精神的编码方式;)【参考方案2】:

这是一种来自纯 HTML/CSS 和工作 sn-p 的方法,将在 span 中键入的值隐藏在 input 集合中的 absolute position 后面。 CSS 可以使spaninput 匹配相同的长度/宽度。拉伸/折叠父级 (label) 将完成这项工作。

感谢@silvenon,您还可以在 sn-p 下方找到一个反应示例

var val = document.querySelector('#test');
let tpl = document.querySelector('#tpl');
let text = val.value;
 tpl.textContent= text;

val.addEventListener("input", function () // onchange ...
  let text= val.value;
  //console.log(text);
  tpl.textContent= text;
  );
label 
  display: inline-block;
  position: relative;
  min-width: 2em;
  min-height: 1.4em;


#tpl 
  white-space: pre;
  /* max-width : could be wised to set a maximum width and overflow:hidden; */


#test 
  font-family: inherit;
  font-size: inherit;
  position: absolute;
  vertical-align: top;
  top: 0;
  left: 0;
  width: 100%;
  background: white;
&lt;label&gt;&lt;span id="tpl"&gt;&lt;/span&gt;&lt;input id='test' value="Some test to try" &gt;&lt;/label&gt;

感谢@silvenon,您可以找到该代码的反应示例。

const SentenceInput = styled.input`
  padding: 0;
  margin: 0;
  border: none;
  border: 1px solid black;
  /* added styles */
  font-family: inherit;
  font-size: inherit;
  position: absolute;
  vertical-align: top;
  top: 0;
  left: 0;
  width: 100%;
  background: white;
`

const Label = styled.label`
  display: inline-block;
  position: relative;
  min-width: 2em;
  min-height: 1.4em;
`

const Template = styled.span`
  white-space: pre;
  /* max-width : could be wised to set a maximum width and overflow:hidden; */
`

const Sentence = ( initialValue ) => 
  const [value, setValue] = React.useState(initialValue)
  return (
    <Label>
      <Template>value</Template>
      <SentenceInput
        type="text"
        value=value
        onChange=(event) => 
          setValue(event.target.value)
        
      />
    </Label>
  )

【讨论】:

OP 很容易将其翻译为 React,但为了完整起见,如果您想编辑答案:gist.github.com/silvenon/0df75af6abb572c646c722341dcf7356 @silvenon,你真是太好了,希望有一天我会足够熟悉 react 这样做。保持安全。 Np ? 我又添加了一个待审核的编辑,我在声明组件时不小心写了StyledInput 而不是SentenceInput。事后看来,我本可以在这里指出错误,而不是让其他人批准编辑。 ? 感谢你们!在浏览了很多不同的资源之后,我实际上最终实现了一个非常不同的解决方案。与这里的内容相比,它涉及的内容要多得多,并且使用可能不适合大多数环境的可编辑 div。所以,我认为我不会发布替代解决方案,至少现在还不会。也就是说,你们俩都提供了非常好的解决方案。 :)【参考方案3】:

&lt;input&gt;size属性可以用来设置宽度。它的工作方式与 ch 单位相同,例如&lt;input size="10"&gt; 的宽度为 10ch。与ch 单位一样,它与等宽字体完美搭配,但在比例间距字体方面只是一个近似值。

要使用按比例间隔的字体获得更准确的测量结果,您可以使用&lt;input&gt; 元素本身的scrollWidth 属性来计算宽度。诀窍是先将值设置为“自动”以在设置值之前捕获内容宽度。如果您想完全适合内容,请将输入 padding 设置为零。

import React,  useEffect, useRef  from "react";

const data = [
   sentence: "Hello world, how are you?" ,
   sentence: "Sometimes in rains in the summer." ,
  
    sentence:
      "Depending where you currently live, it my even rain in the winter."
  
];

const SentenceInput = (props) => 
  const  value, onChange  = props;
  const inputRef = useRef(null);
  useEffect(() => 
    const input = inputRef.current;
    input.style.width = 'auto';
    input.style.width = `$input.scrollWidthpx`;
  );
  return <input ref=inputRef type="text" value=value onChange=onChange />;
;

const Test = () => 
  const handleChange = () => ;

  return (
    <div className="App">
      data.map(( sentence , i) => 
        return (
          <SentenceInput key=i value=sentence onChange=handleChange />
        );
      )
    </div>
  );
;

export default Test;

【讨论】:

以上是关于反应:如何使输入仅与提供的文本量一样宽?的主要内容,如果未能解决你的问题,请参考以下文章

CORS 错误仅与 400 错误请求反应获取请求

如何设置文本输入反应的状态

如何使 ag-grid 列与列中最大的文本一样宽

如何通过反应使输入在漂亮的 dnd 中可拖动

如何从用户定义的输入中上传图像以做出反应?

如何在本机反应中禁用文本输入的字体缩放(可访问性的动态类型)?