【问题标题】:Dynamically adjust input field with minimum width动态调整最小宽度的输入字段
【发布时间】:2018-11-18 13:40:51
【问题描述】:

我编写了以下代码来创建一个文本输入元素,该元素会随着输入长度的增加而动态改变其宽度,但它也有一个最小长度:

var input = document.getElementById("entry");
input.oninput = resizeInput;
resizeInput.call(input);

 function resizeInput() {
    var len = this.value.length, minLen = 30, applied = Math.max(len, minLen);
    this.style.width = applied + "ch";
}
<input id="entry" type="text">

但是,如果您插入一个连续的字符串 (iii.../7/f),您会注意到输入字段的宽度会在输入长度超过最小长度minLen。 (另外,正如 cmets 中所指出的,ggg.../m 或其他人不会出现此问题。我不知道为什么。)

我尝试使用 min-width 属性,但无济于事。

var input = document.getElementById("entry");
input.oninput = resizeInput;
resizeInput.call(input);

function resizeInput() {
    this.style.width = this.value.length + "ch";
}
input { min-width: 30px !important; }
<input id="entry" type="text">

但它也不起作用。那么,我还能做什么呢?

【问题讨论】:

  • 这不是想要的输出吗?如果它动态改变其内容的大小,并且您正在快速添加内容,它不应该快速增长吗?
  • @DBS 为什么我想要在我的文字后面有数英里的空白空间? :( 我不希望左右有任何间距,所以,不,这不是所需的输出。
  • 嗯,我在您的示例中没有看到任何额外的空白
  • @DBS 尝试输入 im。 OP 以字符为单位,但默认字体不是等宽字体。这是主要问题...
  • @DBS 啊,这很有趣。字母g 根本不会引起任何问题,而f1i 等会引起任何问题。这很奇怪。

标签: javascript html css


【解决方案1】:

问题在于以高度为基础的单位ch。由于并非所有字符都具有与宽度相同的高度,因此它可能会在右侧有一个边距(例如使用i)。 https://css-tricks.com/the-lengths-of-css/

您可以通过使用等宽字体或contentEditable 元素来解决字体问题,因为它会自行调整大小。

p{
  border: 1px solid #000;
  display: inline-block;
  min-width: 150px
}
<p contentEditable = 'true'></p>

根据您的需要,您必须更改 css 或脚本以防止粘贴 html 或换行符。

保持input 元素完整性的另一个选项是使用css ::after 为您设置宽度:

.inputText{
  display: inline-block;
}
.inputText > input[type=text]{
  font-size: 13px;
  font-family: arial;
  width: 100%
}

.inputText::after{
  background:red;
  border: 1px solid black;
  content: attr(data-content);
  display: block;
  font-family: arial;
  font-size: 13px;
  visibility: hidden
}
<div class = 'inputText' data-content = 'text'>
  <input type = 'text' placeholder = 'text1' oninput = 'parentNode.setAttribute("data-content", this.value)' />
</div>
<div class = 'inputText' data-content = 'text'>
  <input type = 'text' placeholder = 'text2' oninput = 'parentNode.setAttribute("data-content", this.value)' />
</div>

更新

为它创建了一个小插件,它不能处理所有情况,但基本情况。另外,我用label 替换了div,因为它们更适合input 元素。理论上是可以扩展的。

;(function(ns){
  "use strict";

  //REM: Makes an input-element flexible to its width
  function _makeFlex(input){
    if(input && input.type){
      //REM: Wrapping the input-element with a label-element (makes the most sense on inputs)
      _wrapInputInLabel(input);

      //REM: Adding a listener to inputs, so that the content of othe ::after can be adjusted
      input.addEventListener('input', _onInput, false);
    }
  };

  function _onInput(){
    var tInput = this,
      tParent = tInput.parentNode;

    //REM: Just verifying.. better save than sorry :-)
    if(tInput.type && tParent.tagName === 'LABEL' && tParent.getAttribute('data-flex')){
      //REM: Here exceptions can be set for different input-types
      switch(tInput.type.toLowerCase()){
        case 'password':
          //REM: This one depends on the browser and/or OS
          //https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/password
          tParent.setAttribute('data-flex-content', tInput.value.replace(/./g, '•'))
          break
        default:
          tParent.setAttribute('data-flex-content', tInput.value)
      }
    }
  };

  //REM: Wraps the input in a label-element
  function _wrapInputInLabel(input){
    if(input){
      var tLabel = (input.parentNode && input.parentNode.tagName === 'LABEL') ? input.parentNode : input.parentNode.appendChild(document.createElement('label'));
      tLabel.setAttribute('data-flex', input.getAttribute('data-flex'));
      tLabel.appendChild(input);

      //REM: Copy the font-styles on the label - can be expanded with more
      tLabel.style.fontFamily = window.getComputedStyle(input, null).getPropertyValue('font-family');
      tLabel.style.fontSize = window.getComputedStyle(input, null).getPropertyValue('font-size');

      if(input.id){
        tLabel.setAttribute('for', input.id)
      }
    }
  };

  ns.Flex = {
    Init: function(){
      //REM: Loops through all the marked input elements
      for(let tL=document.querySelectorAll('input[data-flex]'), i=0, j=tL.length; i<j; i++){
        _makeFlex(tL[i])
      }
    }
  }
})(window.mynamespace=window.mynamespace || {});

;window.onload = function(){
  mynamespace.Flex.Init()
};
label[data-flex]{
  font-family: arial;
  font-size: 13px;
  display: inline-block;
  min-width: 30px;
  position: relative
}

label[data-flex]::after{
  background: red;
  border: 1px solid black;
  content: attr(data-flex-content);
  display: block;
  visibility: hidden
  /*REM: following styles are just for demo purposes */
  line-height: 20px;
  visibility: visible;
}

/*REM: Has a little slider on the right */
label[data-flex='number']::after{
  margin-right: 18px
}

label[data-flex] > input{
  font-size: 13px;
  font-family: arial;
  width: 100%
}
<input type = 'text' placeholder = 'text' data-flex = 'flex' id = 'inText' />
<input type = 'text' placeholder = 'crazy' data-flex = 'flex' id = 'inText2' style = 'font-size: 20px; font-family: comic-sans' />
<input type = 'password' placeholder = 'password' data-flex = 'flex' id = 'inPassword' />

<label>
  <input type = 'number' placeholder = 'number' data-flex = 'number' id = 'inNumber' />
</label>

另一个更新

正在研究并进一步尝试,遇到了一个尝试做类似事情的plugin

我提取并稍微更改了input-elements 的部分。对我来说,Chrome 中的所有输入类型都可以正常工作。 IE11 需要在 input-elements 上为 scrollWidth 使用 polyfill,而 Firefox 的类型号存在问题。然而,从使用 clientWidthscrollWidth 的想法来看,我猜这是 - 理论上 - 最好的解决方案:

//REM: Resizes the Input-Element
function resizeInput(input){
  if(input.nodeName.toLowerCase() === 'input'){
    var tStyle = getComputedStyle(input),
        tOffset = 0;

    //REM: Input-Elements with no width are likely not visible
    if(input.getBoundingClientRect().width){
      //REM: Resetting the width for a correct scroll and client calculation
      input.style.width = '0';

      //REM: Calculting the offset
      switch(tStyle.boxSizing){
        case 'padding-box':
          tOffset = input.clientWidth;
          break
        case 'content-box':
          tOffset = parseFloat(tStyle.minWidth);
          break
        case 'border-box':
          tOffset = input.offsetWidth;
          break
      };

      //REM: Somehow IE11 does not seem to support scrollWidth properly
      //https://github.com/gregwhitworth/scrollWidthPolyfill
      var tWidth = Math.max(tOffset, input.scrollWidth - input.clientWidth);

      input.style.width = tWidth + "px";

      //REM: This is kind of a double-check to backtrack the width by setting an unlikely scrollLeft
      for(var i=0; i<10; i++){
        input.scrollLeft = 1000000;

        if(input.scrollLeft === 0){
          break;
        };

        tWidth += input.scrollLeft;
        input.style.width = tWidth + 'px'
      }
    }
    else{
      //REM: Input-Element is probably not visible.. what to do? >.<
      element.style.width = element.value.length + 'ch'
    }
  }
};
input{
  min-width: 30px
}
<input type = 'text' placeholder = 'text' oninput = 'resizeInput(this)' />
<input type = 'number' placeholder = '99' oninput = 'resizeInput(this)' />
<input type = 'password' placeholder = 'password' oninput = 'resizeInput(this)' />

【讨论】:

  • 这个解决方案的问题是你不能利用输入的属性,如 required 或 pattern ,它也不适用于文本(想想密码、电子邮件、号码)。所有这些功能都必须使用额外的 JS 添加。
  • 是的,没错。当然,粘贴的 HTML 也有问题,除非你用脚本解决它——就像我在回答中写的那样。
  • @user3210641:我找到了一个解决方案,它保留了input 的优点,但我还不能让它与其他输入类型一起使用——例如密码有其他字符,因此长度不同: -)
  • 第二次编辑很有趣!为什么你有两个输入呢?
  • @Gaurang Tandon:举个例子。正在玩不同的输入类型。它正在进行中,遗憾的是目前没有时间:-)
【解决方案2】:

虽然@Lain 的答案是最简单的解决方案,但它有多个陷阱 - 如果您不想支持多行输入,则可以监听换行符和文本粘贴,它也不支持输入的本机属性,如 type 、必需、模式等。

您可以做的是为您的输入创建一个容器,并在您的输入字段下隐藏“帮助”元素。然后根据辅助元素的宽度更新输入元素的宽度。

编辑:正如@Mr.Polywhirl 所述,如果您直接更改输入的字体系列或字体大小,我之前的解决方案将不起作用。以下是解决此问题的更新解决方案:

// Save reference of the elements
const input = document.getElementById('input-field');
const helper = document.getElementById('input-text');

// Credit : https://stackoverflow.com/questions/7444451/how-to-get-the-actual-rendered-font-when-its-not-defined-in-css/7444724#7444724
const css = (element, property) => window.getComputedStyle(element, null).getPropertyValue(property);

// Listen to changes on input element
input.addEventListener('input', function(event) {
  // Save input current value
  const value = event.target.value;

  // Get current font styles of the input and set them to helper 
  helper.style.font = css(input, 'font');

  // Update helper text according to the input value
  helper.innerText = value;

  // Update input width to match helper width
  input.style.width = css(helper, 'width');
});
/* Necessary for helper position */
.input-container {
  position:relative;
}

.input-field {
  /* Set initial width */
  width: 5rem;

  /* Set minial width */
  min-width: 5rem;
}

/* Hide helper */
.input-text {
  position: absolute;
  top: 0;
  left: 0;
  z-index: -999;
}
<div class="input-container">
  <input type="text" class="input-field" id="input-field" />
  <span class="input-text" id="input-text"></span>
</div>

【讨论】:

  • 这似乎类似于语法高亮代码编辑器。他们有一个支持文本区域,但在顶部呈现样式 div。唯一的问题是,如果您更改输入的字体样式,则需要将相同的样式应用于助手 span/div。这是一个巧妙的小技巧。
  • 嗯,好点!我没想到...编辑了我的答案,因此它独立于容器的字体大小和字体系列,而是从输入元素计算当前字体样式。
  • 也许它是我的浏览器 (Chrome) 但它对我不起作用。 input 没有扩展。
  • 输入也不适合我。
  • 您的解决方案在哪些方面比其他两种解决方案更好地支持不同的输入类型?对我来说,文本也适用于此。
【解决方案3】:

您可以动态抓取计算出的字体,然后使用 HTML5 画布测量文本。

您尝试以字符为单位进行测量,但字体并不总是等宽字体。您可以通过在示例中输入 im 来测试这一点。您需要测量整个文本并考虑每个字符的宽度。

var input = document.getElementById('entry'); // Grab the element
input.oninput = resizeInput;                  // Add listener
resizeInput.call(input);                      // Trigger change,

function resizeInput() {
  var fontFamily = css(this, 'font-family');
  var fontSize   = css(this, 'font-size');
  var fontStyle  = css(this, 'font-style');

  this.style.width = getTextWidth(this.value, fontSize + ' ' + fontFamily) + 'px';
}

// https://stackoverflow.com/a/7444724/1762224
function css(element, property) {
  return window.getComputedStyle(element, null).getPropertyValue(property);
}

// https://code.i-harness.com/en/q/1cde1
function getTextWidth(text, font) {
  // re-use canvas object for better performance
  var canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas"));
  var context = canvas.getContext('2d');
  context.font = font;
  var metrics = context.measureText(text);
  return metrics.width;
}
input {
  min-width: 30px !important;
}
&lt;input id="entry" type="text"&gt;

【讨论】:

  • 但是您如何衡量与字体系列和大小相关的文本?有没有其他方法可以在不使用画布的情况下调用context.measureText(text)
  • 很酷的解决方案。但我加入了 user3210641,其他解决方案看起来更简单,无需任何计算。
  • 但是您正在破坏&lt;input&gt; 字段的完整性。您不能再使用内置功能,它只是一个段落元素...
  • 在下面查看我的答案。
  • 您的诚信当然是正确的。我只是喜欢其他解决方案的简单性。只是我个人的口味。
猜你喜欢
  • 2013-05-03
  • 2019-04-01
  • 2011-03-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多