上周工作中在设计图上看到了这样一个 textarea 框,只有底下一条线,没有高度
也就是说输入框高度不是固定的,是由输出内容决定的
思路
看到这个设计想了想没思路,立即去找度娘,网上支流解决方案有 2 种:
- 监听
input
事件获取textarea
的滚动高度,调节款式 - 属性
contenteditable
让height: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 事件。