Skip to content

高度自适应输入组件实践

TIP

最近在业务开发需要制作一些表单表格页面,由于场景需求无法用到编译时的能力,且其中一些地方要求实现支持多行输入并且高度自适应的文本框,在经过一些社区文章翻阅后,决定使用contenteditable来做这个封装

一些对比

Textarea

textarea天然对多行文本输入有着良好的支持,还有对行数,最大字符等属性支持以及Vue绑定语法糖的支持。但由于其高度无法自适应,需要借助scrollHeight做监听支持,页面初始化时的字符计算等

Ep-Input

ElementPlus的Input组件支持多行输入模式,没有细看其中的实现,大概理解为是通过字符数计算动态改变了组件高度(不一定准确), 但页面是仅基于Vue.js进行手动封装, r没有引入Ep组件库,放弃该方案

Contenteditable

NOTE

该属性是富文本支持,在需要仅文本输入的情况下应该将属性值设置plaintext-only

contenteditable 属性可以让元素天然支持富文本输入,且元素由于文本改变在最大宽度限制下可以自动撑开内容,在不需要过多的表单组件属性支持时,使用contenteditable非常简单,需要做的仅仅是将其变为支持双向绑定的输入组件

组件实现

代码实现是基于非SFC组件下使用Vue框架

选项式

js
const { h } = Vue
const childAreaInput = {
  data() {
    return {
      msg:this.modelValue
    }
  },
  props:{
    modelValue:{
      type:String,
      default:''
    }
  },
  watch: {
    // 监听modelValue的变化,同步更新msg
    modelValue(newVal) {
      this.msg = newVal;
    }
  },
  methods: {
    // 更新变量值
    AreaInput() {
      this.$emit('update:modelValue', this.$refs.AreaInput.textContent);
    },
  },
  render() {

    return h('div',
    {
      contenteditable: true,
      onInput: this.AreaInput,
      ref: 'AreaInput',
      style:{
        whiteSpace:'pre-wrap',
        width:'100%',
      }
    },
    this.msg)
  }
}

组合式

js
const {defineComponent,ref,watch,h} = Vue

const useAreaInput = (props,emit) => {
    const msg = ref(props.modelValue)

    const areaInput = (event) => {
      emit('update:modelValue',event.target.textContent)
    }

    watch(()=>props.modelValue,(newValue)=>{
      msg.value = newValue
    })

    return {msg,areaInput,submitData}
}

const childAreaInputByComposable = defineComponent({
  props:{
    modelValue:{
      type:String,
      default:''
    }
  },
  emits:['update:modelValue'],
  setup(props,{emit}){
    const { msg, areaInput } = useAreaInput(props, emit);

    return ()=>{
      const { h } = Vue

      return h('div',
      {
        contenteditable: true,
        onInput: areaInput,
        ref: 'AreaInput',
        style:{
          whiteSpace:'pre-wrap',
          width:'100%',
        }
      },
      msg)
    }
  }
})

使用

js
const MainApp = {
  el: "#app",
  components: {
    AreaInput: childAreaInput,
    AreaInputByComposable:childAreaInputByComposable
  }
}

// 实例化
const app = Vue.createApp(OQC)
// 挂载
app.mount("#app")
html
<Area-Input v-model="mainData.custom" />

<Area-Input-By-Composable v-model="mainData.custom"/>