【问题标题】: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

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

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

有什么想法吗?

【问题讨论】:

    标签: css reactjs styled-components


    【解决方案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.scrollWidth}px`;
      });
      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;
    

    【讨论】:

      猜你喜欢
      • 2011-06-01
      • 2020-12-17
      • 2015-05-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-05
      相关资源
      最近更新 更多