上周工作中在设计图上看到了这样一个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事件。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。