高度自适应的输入框
有时候我们需要一个高度能随内容自动增加的输入框,input
显然不行,因为 input
里的文字是不换行的。文本域 textarea
里的文字倒是换行的,可一旦文字内容超过其高度,textarea
就会增加一个烦人的滚动条,这是很影响视觉的,就如同下面:
<textarea cols="30" rows="3"></textarea>
那么有没有办法制作一个高度能随文字内容自动增加的输入框呢?答案是肯定的,下面介绍两种方式。
方式一
这种方式依然使用 textarea
, 主要思想是我们将 textarea
放入一个容器中,同时在这个容器中放入一个隐藏的 div (visibility: hidden), 监听 textarea
的输入事件并将其中的文字动态的同步到隐藏的div中,这样div 就可以撑开容器,这时设置 textarea
的高度为 100% 并将其定位到容器的左上角,那么 textarea
的高度自然就是其中文字内容的高度了。
visibility
是一个CSS属性,用来在不更改文档的布局的前提下显示或隐藏元素,它有三个可能的取值:
visible
元素正常显示(默认值);hidden
隐藏元素,但是其他元素的布局不改变,相当于此元素变成透明。
若将其子元素设为 visibility: visible,则该子元素依然可见;collapse
用于表格的行、列、列组和行组,隐藏表格的行或列,并且不占用任何空间。
<!-- demo-1.html -->
<div class="container">
<div></div>
<textarea placeholder="输入消息..."></textarea>
</div>
<style>
.container {
width: 500px;
position: relative;
}
.container div {
visibility: hidden;
/** 避免初始化时容器没有高度 */
padding: 8px 0px;
}
.container textarea {
width: 100%;
height: 100%;
position: absolute;
top: 0px;
padding: 0px;
/** 必须设置为 content-box !!! */
box-sizing: content-box;
}
</style>
<script>
const textarea = document.querySelector(\'.container textarea\');
const div = document.querySelector(\'.container div\');
textarea.addEventListener(\'input\', (e) => {
div.innerText = e.target.value;
});
</script>
查看样例:https://mxsyx.site/archives/10/#demo-1
你可能已经注意到了,当我们输入文字时,输入框的高度显然要比文字内容高许多,伴随着输入文字的增多。高度差会越来越大,这是因为隐藏 div
与 文本域 textarea
内字体的尺寸与行高是不同的, div
内的字体尺寸与行高要比 textarea
内的大,所以 div
撑开的容器高度自然要高于 textarea
内的文字内容高度。要解决这个问题,统一它们的字体尺寸与行高就可以了。(注:div
的字体尺寸与行高默认继承自父元素)
<!-- demo-1.html -->
<div class="container">
<div></div>
<textarea placeholder="输入消息..."></textarea>
</div>
<style>
.container {
width: 500px;
position: relative;
font-size: 14px;
line-height: 16px;
}
.container div {
visibility: hidden;
/** 避免初始化时容器没有高度 */
padding: 8px 0px;
}
.container textarea {
width: 100%;
height: 100%;
position: absolute;
top: 0px;
padding: 0px;
/** 必须设置为 content-box !!! */
box-sizing: content-box;
/** 设置字体尺寸与行高继承自父元素 */
font-size: inherit;
line-height: inherit;
/* 去掉右下角的调整大小的标志 */
resize: none;
}
</style>
<script>
const textarea = document.querySelector(\'.container textarea\');
const div = document.querySelector(\'.container div\');
textarea.addEventListener(\'input\', (e) => {
div.innerText = e.target.value;
});
</script>
查看样例:https://mxsyx.site/archives/10/#demo-2
这样一来高度就一致了。这种方式虽然可以较好的实现高度自适应的输入框,但实现起来总感觉很粗糙,下面这种方式就明显简单多了。
方式二
像 div
, p
, blockquote
这样的元素默认是不可编辑的,但我们可以将其 contenteditable
属性设置为 true
, 使其变为可编辑的。
contenteditable
是一个全局属性,用于指示元素是否可被用户编辑,该属性必须采用以下值之一:
true
或者 \’空字符串\’, 表示该元素是可编辑的;false
, 表示该元素是不可编辑的。- 如果未设置此属性,则其默认值将从其父元素继承。
<div class="container" contenteditable="true"></div>
<style>
.container {
width: 500px;
font-size: 14px;
line-height: 16px;
border: solid 1px #999;
}
</style>
尝试输入一段文字吧:
.container-2 { width: 500px; font-size: 14px; line-height: 16px; border: 1px solid rgba(153, 153, 153, 1) }
是不是很简单呢? 我们也可以使用CSS伪类 :empty, :focus, 实现placeholder
那样的效果
<style>
.container {
width: 500px;
font-size: 14px;
line-height: 16px;
border: solid 1px #999;
}
.container:empty::before {
content: "输入消息...";
color: #999999;
}
.container:focus::before {
content: none;
}
</style>
尝试输入一段文字吧:
.container-3 { width: 500px; font-size: 14px; line-height: 16px; border: 1px solid rgba(153, 153, 153, 1) }
.container-3:empty::before { content: “输入消息…”; color: rgba(153, 153, 153, 1) }
.container-3:focus::before { content: none }
如果你使用 Vue.js, 我们也可以它封装为一个Vue组件:
<template>
<div
class="msg-input"
contenteditable="true"
@input="changeText"
>{{ innerText }}</div>
</template>
<script>
export default{
name: "MsgInput",
props: [\'value\'],
data: function() {
return {
innerText: this.value,
}
},
methods: {
changeText() {
this.$emit(\'input\', this.$el.innerText);
}
}
}
</script>
<style scoped>
.msg-input {
width: 500px;
font-size: 14px;
line-height: 16px;
border: solid 1px #999;
}
.msg-input:empty::before {
content: "输入消息...";
color: #999999;
}
.msg-input:focus::before {
content: none;
}
</style>
接下来在父组件中引用这个组件:
<template>
<div>
<MsgInput v-model="msg"/>
</div>
</template>
<script>
import MsgInput from "/MsgInput.vue";
export default {
name: "MsgToolkit",
data: function() {
return {
msg: \'\'
}
},
components: {
MsgInput
},
}
</script>
父组件为子组件使用 v-model
指令,将子组件的 value
与 父组件的 msg
双向绑定在一起。当输入事件发生后,子组件调用changeText方法,触发一个 input
事件,父组件监听到此事件,将事件传递过来的数据同步到 msg
上,由于数据是双向绑定的,子组件的 value
值也会相应发生变化。更过原理请参考 自定义组件的-v-model
该篇博客内的代码已同步到Github
参考资料:
[1]. MDN文档 https://developer.mozilla.org/zh-CN/docs/Web/HTML/Global_attributes/contenteditable
[2]. MDN文档 https://developer.mozilla.org/zh-CN/docs/Web/CSS/visibility
[3]. MDN文档 https://developer.mozilla.org/zh-CN/docs/Web/CSS/:empty
[4]. MDN文档 https://developer.mozilla.org/zh-CN/docs/Web/CSS/:focus
[5]. Vue.js官方文档 https://cn.vuejs.org/v2/guide/components-custom-events.html#自定义组件的-v-model