前言
本篇文章所有的代码,都是在 vue + vite + ts 项目基础之上实现的,这样也是为了方便大家直接用源码,在开始之前建议大家阅读这篇《零基础搭建 vite项 目教程》。此项目就是这个教程搭建的,本篇文章关于输入框的相关代码是此项目的一个分支(content-input)。如果你没有时间阅读详细的教程,你也可以直接在 git 上克隆项目。
搭建简单的vite+ts+vue框架
https://gitee.com/yangjihong2113/learn-vite
本篇文章的所有代码都在 content-input 分支上,基本内容大致如下(后续代码可能会有优化,建议以最新的代码为准):
把项目克隆之后切换到 content-input 分支,运行 pnpm install ,然后运行 npm run dev 直接访问 http://localhost:80/contentInput 就可以看到本篇文章涉及的全部内容。
注意,需要使用 pnpm 命令安装包,因为我的这个项目是使用 pnpm 构建的,否则会报错,如下图。
本篇文章的主要内容是文本输入,也就是输入文字的输入框的实现和应用场景。前端关于输入框的实现主要分为四种(1)input 标签(2)textarea 标签 (3)contenteditable 属性 (4)富文本编辑器插件。
除了文本输入,前端的输入形式还有文件、图片、视频、音频等,我们在本篇文章暂时不考虑。
一、单行文本 Input 标签
h5 的原生 input 标签是最常用的单行文本输入功能,一般简单的功能用原生标签就可以,但是我们在使用的过程中,除了要自己修改样式,还有一些注意事项。
1.1 原生 input 标签
- input 标签是自闭合标签
- input 标签是行内元素
- 内容只能展示一行,不能换行
- 当高度小于字体大小的时候,上下内容会隐藏,不能设置 overflow,因为它是行内元素
- 需要设置背景色,因为 input 有默认的白色背景
- 需要设置 outline: none,否则会有蓝色轮廓框
- input 标签还可以输入文件类型
1.2 组件库
有的时候我们需要给输入框增加前缀图标,或者是清空按钮,除了可以自己封装,还可以使用组件库,但是都需要自己调整样式。
1.3 源代码
<template>
<div class="page-container">
<h1 class="title">input 标签输入文本</h1>
<ol>
<li>自闭合标签</li>
<li>行内元素</li>
<li>内容只能展示一行,不能换行</li>
<li>当高度小于字体大小的时候,上下内容会隐藏,不能设置 overflow,因为他是行内元素</li>
<li>需要设置背景色,因为input 有默认的白色背景</li>
<li>需要设置 outline: none,否则会有蓝色轮廓框</li>
</ol>
<div class="sample-box">
<div class="sample-label">一个默认的输入框</div>
<input v-model="myName" placeholder="请输入名称" />
<div class="sample-label">设置了样式的输入框</div>
<input v-model="myName" class="input-ele" placeholder="请输入名称" />
<div class="sample-label">字体大于标签高度,上下内容会隐藏,不能设置 overflow</div>
<input v-model="myName" class="input-ele input-2" placeholder="请输入名称" />
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue'
const myName = ref('你好')
</script>
<style lang="scss" scoped>
.page-container {
padding: 24px;
ol {
list-style: decimal;
padding-left: 14px;
}
.title {
margin: 10px 0;
}
.sample-box {
margin-top: 10px;
padding: 10px 20px;
width: 500px;
border-radius: 6px;
background: #eee;
.sample-label {
margin: 10px 0 5px;
}
.input-ele {
border: 1px solid;
width: 100%;
outline: none; // 输入框的轮廓特别明显
background: transparent; // 一般都需要设置背景色,要不然默认为白色
&.input-2 {
display: inline-block;
overflow: auto;
height: 32px;
font-size: 200px;
}
}
}
}
</style>
二、多行文本 textarea 标签
textarea 原生标签支持多行文本的输入,在使用常规功能的时候同样也有一些注意事项。
2.1 原生 textarea 标签
- 自闭合标签
- 块级元素
- 可以自动换行,可以展示多行文本
- 当高度小于字体大小的时候,默认会滚动,也可以设置 overflow:hidden,不让它滚动
- 需要设置背景色,因为 textarea 有默认的白色背景
- 需要设置 outline: none,否则会有蓝色轮廓框
2.2 让高度随着内容动态变化
原生的 textarea 标签默认文本很长的时候滚动展示,不会撑起高度,除非我们手动给 textarea 设置高度,但是也无法实现输入框的内容随着内容动态变化。
2.2.1 实现高度自适应
(1)基本思路
输入框高度动态变化的需求很常见,如:网站的评论,我们需要实现这个功能,基本思路是
- 给 textarea 元素设置一个 height 默认高度
- 给 textarea 元素设置一个 min-height、max-height 最大、最小高度
- 监听 input 事件
- input 事件触发,获取 event.srcElement.scrollHeight
- 根据上述的 scrollHeight 更新 textarea 元素的高度
但是这样写是有问题的
- 这样写有一个问题,event.srcElement.scrollHeight 并不是删除文字之后的高度,导致在选中大量的文字之后删除,textarea 元素的高度并不能更新为文字删除后的高度
- 除了使用 input 事件,还有一个办法是使用 vue 中的 watch 监听输入框值的变化,但是同样无法准确根据文字内容更新高度
- 因为我们高度的获取和设置都是针对同一个 textarea 的 dom
- 解决办法:克隆一个一摸一样的 textarea(第三章说)
(2)核心代码
<textarea ref="inputEle5" v-model="myName" class="input-ele input-4" placeholder="请输入名称" @input="input" />
const input = (e: any) => {
if (!inputEle5.value) {
return
}
const scrollHeight = e.srcElement.scrollHeight
inputEle5.value.style.height = `${scrollHeight}px`
}
这里面我们提到了 scrollHeight 即 文本内容的高度,我们应该知道 textarea 的scrollHeight 代表着所有文本内容的高度 + 上下 padding
2.2.2 最少 3 行,最多 5 行
(1)基本思路
- 可以参考上面 2.2.1 的办法
- 需要设置 min-height 和 max-height
- 最小高度 = 单行的高度 * 3 (最少行数)
- 最大高度 = 单行的高度 * 5(最多行数)
但是这样写同样有问题
- 除了 2.2.1 中的问题,还有个问题就是如何知道单行的高度?
- 单行的高度 = textarea 的 line-height
- 如果我们写死最小高度和最大高度,我们在改变 line-height 都需要重新改一下最小高度和最大高度,这很麻烦
- 解决办法:克隆一个一摸一样的 textarea(第三章说)
(2)核心代码
<textarea ref="inputEle6" v-model="myName" rows="3" class="input-ele input-6" placeholder="请输入名称" @input="input2" />
const input2 = (e: any) => {
console.log(e)
if (!inputEle5.value) {
return
}
const scrollHeight = e.srcElement.scrollHeight
inputEle6.value.style.height = `${scrollHeight}px`
}
&.input-6 {
width: 200px;
height: 72px;
min-height: 72px; // line-height: 24 ,3行 72px
max-height: 120px; // line-height: 24 ,5行 120px
}
2.3 组件库
组件库提供了很完善的多行文本输入功能,包括支持高度自适应,支持最少输入的行数、最大输入的行数,所以有了组件库我们就不用自己费劲实现多行文本输入框的高度自适应了。
2.4 源代码
<template>
<div class="page-container">
<h1 class="title">textarea 标签输入文本,常规功能</h1>
<ol>
<li>自闭合标签</li>
<li>块级元素</li>
<li>可以自动换行,可以展示多行文本</li>
<li>当高度小于字体大小的时候,默认会滚动,也可以设置 overflow:hidden,不让它滚动</li>
<li>需要设置背景色,因为 textarea 有默认的白色背景</li>
<li>需要设置 outline: none,否则会有蓝色轮廓框</li>
</ol>
<div class="sample-box">
<div class="sample-label">1. 一个默认的输入框</div>
<textarea v-model="myName" placeholder="请输入名称" />
<div class="sample-label">2. 设置了样式的输入框</div>
<textarea v-model="myName" class="input-ele" placeholder="请输入名称" />
<div class="sample-label">3. 设置了宽度的输入框,高度默认展示两行,内容多的时候高度不会自动调整,会滚动展示</div>
<textarea v-model="myName2" class="input-ele input-2" placeholder="请输入名称" />
<div class="sample-label">4. 设置了宽度的输入框,不设置高度,展示指定的行数:不管内容如何变化,始终展示3行</div>
<textarea v-model="myName2" rows="3" class="input-ele input-3" placeholder="请输入名称" />
<div class="sample-label">5. 让高度随着内容动态变化</div>
<div>实现步骤:</div>
<ol>
<li>给 textarea 元素设置一个 height 默认高度</li>
<li>给 textarea 元素设置一个 min-height、max-height 最大、最小高度</li>
<li>监听 input 事件</li>
<li>input 事件触发,获取 event.srcElement.scrollHeight</li>
<li>根据上述的 scrollHeight 更新 textarea 元素的高度</li>
</ol>
<div>问题:</div>
<ol>
<li>这样写有一个问题,event.srcElement.scrollHeight 并不是删除文字之后的高度,导致在选中大量的文字之后删除,textarea 元素的高度并不能更新为文字删除后的高度</li>
<li>除了使用 input 事件,还有一个办法是使用 vue 中的 watch 监听输入框值的变化,但是同样无法准确根据文字内容更新高度</li>
<li>因为我们高度的获取和设置都是针对同一个 textarea 的 dom</li>
<li>解决办法:克隆一个一摸一样的 textarea(下面说)</li>
</ol>
<textarea ref="inputEle5" v-model="myName" class="input-ele input-4" placeholder="请输入名称" @input="input" />
<div class="sample-label">6. 让高度随着内容动态变化,最少展示3行,最多5行</div>
<div>实现步骤:</div>
<ol>
<li>可以参考 5 中办法</li>
<li>需要设置 min-height 和 max-height</li>
<li>最小高度 = 单行的高度 * 3 (最少行数)</li>
<li>最大高度 = 单行的高度 * 5(最多行数)</li>
</ol>
<div>问题:</div>
<ol>
<li>除了 5 中的问题,还有个问题就是如何知道单行的高度?</li>
<li>单行的高度 = textarea 的 line-height</li>
<li>如果我们写死最小高度和最大高度,我们在改变 line-height 都需要重新改一下最小高度和最大高度,这很麻烦</li>
<li>解决办法:克隆一个一摸一样的 textarea(下面说)</li>
</ol>
<textarea ref="inputEle6" v-model="myName" rows="3" class="input-ele input-6" placeholder="请输入名称" @input="input2" />
<div class="sample-label">7. 让高度随着内容动态变化使用组件库</div>
<div>组件库如 elementPlus 或 antd 都提供了 autosize 的输入框,还能设置最大行数和最小行数</div>
<ol>
<li>一半情况使用组件库就能满足我们的要求了,但