以最小宽度动态调整输入字段

时间:2018-06-08 13:20:56

标签: javascript html css

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

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。 (另外,正如评论中指出的那样,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">

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

3 个答案:

答案 0 :(得分:3)

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

您可以使用等宽字体或contentEditable元素解决字体问题,因为它有点调整自身的大小。

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

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

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

&#13;
&#13;
.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
}
&#13;
<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>
&#13;
&#13;
&#13;

更新

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

&#13;
&#13;
;(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()
};
&#13;
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%
}
&#13;
<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>
&#13;
&#13;
&#13;

另一次更新

正在进行研究和尝试,并遇到试图做同样事情的plugin

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

&#13;
&#13;
//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'
    }
  }
};
&#13;
input{
  min-width: 30px
}
&#13;
<input type = 'text' placeholder = 'text' oninput = 'resizeInput(this)' />
<input type = 'number' placeholder = '99' oninput = 'resizeInput(this)' />
<input type = 'password' placeholder = 'password' oninput = 'resizeInput(this)' />
&#13;
&#13;
&#13;

答案 1 :(得分:2)

虽然@Lain的答案是最简单的解决方案,但它有多个陷阱 - 如果你不想支持多行输入,你就会听取换行符以及粘贴文字,而且它也没有#39 ; t支持输入的本机属性,如类型,必需,模式等。

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

编辑:正如@ Mr.Polywhirl所提到的,如果您直接更改输入的font-family或font-size,我以前的解决方案将无法运行。以下是解决此问题的更新解决方案:

&#13;
&#13;
// 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');
});
&#13;
/* 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;
}
&#13;
<div class="input-container">
  <input type="text" class="input-field" id="input-field" />
  <span class="input-text" id="input-text"></span>
</div>
&#13;
&#13;
&#13;

答案 2 :(得分:1)

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

您正在尝试以字符进行度量,但字体并不总是等宽字体。您可以在示例中键入i vs m来测试此问题。您需要测量整个文本并考虑每个字符的宽度。

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;
}
<input id="entry" type="text">