上周工作中在设计图上看到了这样一个textarea框,只有底下一条线,没有高度
image
也就是说输入框高度不是固定的,是由输入内容决定的

思路

看到这个设计想了想没思路,立刻去找度娘,网上主流解决方案有2种:

  • 监听input事件获取textarea的滚动高度,调节样式
  • 属性contenteditableheight:auto的div可以编辑内容,取代textarea

1.通过事件调节高度

<template>
  <div class="flexable-textarea">
  <div ref="simulateTextarea" :style="{'padding':padding}" class="simulate-textarea">{{ value || '0' }}</div>
    <textarea
      ref="editTextarea"
      :value="value"
      :maxlength="maxLength"
      :style="{'padding':padding}"
      :placeholder="placeholder"
      class="edit-textarea"
      @input="handleInput($event)">
    </textarea>
  </div>
</template>

<script>
/* 高度自适应的textarea */
export default {
  name: 'FlexableTextarea',
  props: {
    padding: {
      type: String,
      default: '20px 0'
    },
    value: {
      type: String,
      default: ''
    },
    maxLength: {
      type: Number,
      default: 999999
    },
    placeholder: {
      type: String,
      default: ''
    }
  },
  watch: {
    value(val, oldVal) {
      if (val !== oldVal) {
        this.check()
      }
    }
  },
  mounted() {
    this.check()
  },
  methods: {
    // 检测高度
    check() {
      this.$nextTick(() => {
        const textarea = this.$refs.editTextarea
        const simulate = this.$refs.simulateTextarea
        if (textarea.style.height !== `${simulate.scrollHeight}px`) {
          textarea.style.height = `${simulate.scrollHeight}px`
        }
      })
    },
    handleInput(event) {
      if (event.target.value !== this.value) this.check()
      this.$emit('input', event.target.value)
    }
  }
}
</script>
<style lang="scss" scoped>
.flexable-textarea {
  position: relative;
  overflow: hidden;
  font-size: 28px;
  line-height: 48px;
  padding: 0;
  .edit-textarea {
    box-sizing: border-box;
    font-size: inherit;
    line-height: inherit;
    white-space: pre-wrap;
    overflow-wrap: break-word;
    display: block;
    width: 100%;
    height: auto;
    min-height: 48px;
  }
  .simulate-textarea {
    box-sizing: border-box;
    position: absolute;
    left: 0;
    top: 0;
    z-index: -1;
    opacity: 0;
    width: 100%;
    height: auto;
    min-height: 48px;
    font-size: inherit;
    line-height: inherit;
    white-space: pre-wrap;
    overflow-wrap: break-word;
  }
}
</style>

HTML结构并不复杂,但有人会问为什么不直接获取textarea的高度,还要做个隐藏的div容器把value再复制一遍呢?因为textarea的特性是可以被撑开,但不会自己收缩,设置样式height:auto在输入很多行后再删除几行,它的高度是不会变的。所以需要借助其他容器拿到scrollHeight,曲线救国。
优点:
兼容性好
缺点:
1.设置高度时部分浏览器有卡顿感。
2.如果组件一开始隐藏再显示,需要手动调用check方法,不够干净。

2.用div替代textarea

<template>
  <div :style="{padding}" class="flex-input-wrapper" @click.stop="onFocus($event)">
    <div
      ref="flexInput"
      :placeholder="placeholder"
      class="flex-input"
      contenteditable="true"
      @input="changeText($event)"
    ></div>
  </div>
</template>
<script>
/* 高度自适应的input */
export default {
  name: 'FlexableInput',
  props: {
    padding: {
      type: String,
      default: '20px 0'
    },
    value: {
      type: String,
      default: ''
    },
    maxLength: {
      type: Number,
      default: 999999
    },
    placeholder: {
      type: String,
      default: ''
    }
  },
  watch: {
    value(newValue) {
      const ele = this.$refs.flexableInput
      const innerText = ele.innerText
      if (newValue !== innerText) {
        this.setValue(newValue)
      }
    }
  },
  mounted() {
    this.setValue(this.value)
  },
  methods: {
    setValue(value = '') {
      if (value.length === 0) return
      const _val = value.length < this.maxLength ? value : value.substring(0, this.maxLength)
      this.$refs.flexableInput.innerText = _val
    },
    changeText(event) {
      const ele = event.target
      let innerText = ele.innerText
      if (innerText.length > this.maxLength) {
        innerText = innerText.substring(0, this.maxLength)
        ele.innerText = innerText
        this.keepLastIndex(ele)
      }
      this.$emit('input', innerText)
    },
    onFocus(event) {
      const input = this.$refs.flexableInput
      if (document.activeElement === input) return
      this.keepLastIndex(input)
    },
    // 固定光标到最后
    keepLastIndex(obj) {
      if (window.getSelection) {
        // ie11 10 9 ff safari
        obj.focus() // 解决ff不获取焦点无法定位问题
        const range = window.getSelection() // 创建range
        range.selectAllChildren(obj) // range 选择obj下所有子内容
        range.collapseToEnd() // 光标移至最后
      } else if (document.selection) {
        // ie10 9 8 7 6 5
        const range = document.selection.createRange() // 创建选择对象
        // var range = document.body.createTextRange();
        range.moveToElementText(obj) // range定位到obj
        range.collapse(false) // 光标移至最后
        range.select()
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.flexable-input {
  outline: none;
  user-select: text;
  cursor: text;
  width: 100%;
  font-size: 28px;
  line-height: 48px;
  white-space: pre-wrap;
  overflow-wrap: break-word;
  &:empty::before {
    content: attr(placeholder);
    color: #999;
  }
}
</style>

代码更加简单了,也没有前一种方案的缺点。唯一的瑕疵是点击不够灵敏,div经常获取不到焦点,因此加上了click事件。


风笛
495 声望6 粉丝