内容简介:本来不打算写输入框的分析,心想一个输入框能有多复杂,还能怎么封装,后来浏览了下源码,发现还是有很多自己不知道的知识点,于是打算还是写,下图就是一个Element的最基本的输入框结果一看源码,我的鬼鬼,源码竟然300多行!咋会这么复杂,看过官网的文档后,发现确实应该这么复杂,因为这个输入框不仅仅是只有一个input这么简单,还附带了很多的其他内容,上图仅是一个最基本的形式而已,下面我们依次分析,官网源码本来打算贴出全部源码,但是发现这样篇幅太长,因此我们只分析重点,分析部分源码
本来不打算写输入框的分析,心想一个输入框能有多复杂,还能怎么封装,后来浏览了下源码,发现还是有很多自己不知道的知识点,于是打算还是写,下图就是一个Element的最基本的输入框
结果一看源码,我的鬼鬼,源码竟然300多行!咋会这么复杂,看过官网的文档后,发现确实应该这么复杂,因为这个输入框不仅仅是只有一个input这么简单,还附带了很多的其他内容,上图仅是一个最基本的形式而已,下面我们依次分析,官网源码 点此
本来打算贴出全部源码,但是发现这样篇幅太长,因此我们只分析重点,分析部分源码
输入框源码html结构
首先还是先要搞懂Element封装后的input的html结构才行,下面是简化后的html结构
<template> <div ...> <template v-if="type !== 'textarea'"> <!-- 前置元素 --> <div class="el-input-group__prepend" v-if="$slots.prepend"> <slot name="prepend"></slot> </div> <!--主体input--> <input ...> <!-- 前置内容 --> <span class="el-input__prefix" v-if="$slots.prefix || prefixIcon"> ... </span> <!-- 后置内容 --> <span ... </span> <!-- 后置元素 --> <div class="el-input-group__append" v-if="$slots.append"> ... </div> </template> <textarea v-else> </textarea> </div> </template> 复制代码
是不是看着很头大?其实很简单,最外层一个div作为wrapper包裹里面的元素,然后里面是template标签(template实际不会渲染出来)的v-if,最下面是textarea的v-else,说明 type
这个选项控制输入框组件是显示 input
还是 textarea
,对于v-else就一个textarea,没啥可说的,关键在于前面的v-if,仔细看这个结构,是由 前置元素,主体input,前置内容,后置内容,后置元素 这几部分构成,那么它们分别代表啥呢?下图就是答案
图中中间的是input输入框,前后2个都是辅助性的内容,这2个就是前后置元素,而输入框内的搜索和日期Icon就是前后置内容,因此要封装这么个完整的input,代码量确实比较多
这里值得注意的是前后置元素和input主体的 布局 ,修改前后置元素内容可以发现,中间input的宽度是自适应的,如下图
中间input自动变窄,那么这哥布局是咋回事呢,这哥布局类似于 左列宽度不定,右列自适应 ,左列不定的意思是宽度由内容撑开来,查看css代码得知,这是table-cell
布局,我们知道table内表格宽度都是自适应的,某一列很宽的话,另外的列就会变窄,因此这个思想可以用到这里来,下面就是示例布局(左列宽度不定,右列自适应),注意外层容器设置
display:table
<div style="display:table" class='wrapper'> <div style="display:table-cell" class='left'> </div> <div style="display:table-cell" class='right'> </div> </div> 复制代码
这个布局用flex也可以实现,具体就是left元素不设置宽度,right元素设置flex:1即可,下面看下输入框的css
输入框其实是有左右padding的,为了更美观,这里不是用text-indent来控制光标位置
可以看出-webkit-appearance:none,outline:none
这些用法在和各个组件内都很普遍,目的就是去掉浏览器自己渲染出的样式,统一规定样式。这里的
transition
居然使用了贝塞尔曲线进行过渡,话说过渡时间才0.2秒,使用贝塞尔曲线能看出来么?直接
ease
应该也可以啊!
禁用状态的实现
禁用很简单,通过用户传入的 disabled
属性来控制,如下代码
<el-input placeholder="请输入内容" v-model="input1" :disabled="true"> </el-input> 复制代码
源码里通过 <input :disabled="inputDisabled" ...>
来控制input的功能禁用,这个 inputDisabled
是个计算属性
inputDisabled() { return this.disabled || (this.elForm || {}).disabled; }, 复制代码
这里因为要判断如果input被包含在表单内,如果表单禁用,那么自然自己也就被禁用了。输入框样式上的禁用是由最外层的div的class控制的
<div :class=[{'is-disabled': inputDisabled}...]>...</div> 复制代码
这里没有放在里面的input上进行控制,原因是放在最外层可以统一控制里面的textarea和input,减少代码冗余,通过子选择器选择到input和textarea进行控制,这里 placeholder
的颜色也是可以控制的,但要注意兼容性
&::placeholder { color: $--input-disabled-placeholder-color; } 复制代码
input元素的属性
通过查看组件里原生input的属性,了解了很多知识点
<input :tabindex="tabindex" v-if="type !== 'textarea'" class="el-input__inner" v-bind="$attrs" :type="type" :disabled="inputDisabled" :readonly="readonly" :autocomplete="autoComplete" :value="currentValue" ref="input" @compositionstart="handleComposition" @compositionupdate="handleComposition" @compositionend="handleComposition" @input="handleInput" @focus="handleFocus" @blur="handleBlur" @change="handleChange" :aria-label="label" > 复制代码
哇,居然这么多属性和方法~~这就是一个成熟组件需要实现的东西,先看 tabindex
,就是控制tab键按下后的访问顺序,由用户传入tabindex如果设置为负数则无法通过tab键访问,设置为0则是在最后访问。然后 v-if="type !== 'textarea'"
控制了这个input的渲染与否,用户传入type属性进行控制,然后是input的类 el-input__inner
,前面介绍过,然后是 v-bind="$attrs"
这句话,这句话是干嘛的?翻开官网得知
读起来很拗口,下面用个例子说明
<el-input maxlength="5" minlength="2"> </el-input> 复制代码
这里我们给 <el-input>
组件添加了2个 原生 属性,注意这2个原生属性并没有在prop里面,这2个属性是控制input的最大输入和最小输入长度的,那么这2个属性现在仅仅放在了父元素 <el-input>
上,如何将其传递给素 <el-input>
内的原生input子元素呢?不传递则这2个属性不起作用,因为子input上没有这2个属性。答案就是通过 v-bind="$attrs"
来实现,它将父元素所有 非prop 的特性都绑定在了子元素input上,否则你还得在props里声明maxlength,minlength,代码量增大。这就是 $attrs
的优势所在
往下看 :readonly="readonly" :autocomplete="autoComplete"
,这2个属性都是原生的属性,由用户传入,控制输入框只读和是否自动补全,然后是输入框的value :value="currentValue"
这里的currentValue是在data里面
currentValue: this.value === undefined || this.value === null ? '' : this.value, 复制代码
如果用户没有在 <el-input>
上写v-model(v-model原理参考官网),那么就没有传入value,所以currentValue就是空字符串,否则就是传入的值,接着 ref="input"
一句,ref用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上,这是为了方便后续代码直接拿到原生input的dom
然后是这3句话
@compositionstart="handleComposition" @compositionupdate="handleComposition" @compositionend="handleComposition" 复制代码
这可不能小瞧,这3个方法是原生的方法,这里简单介绍下,官方定义如下 compositionstart 事件触发于一段文字的输入之前(类似于 keydown 事件,但是该事件仅在若干可见字符的输入之前,而这些可见字符的输入可能需要一连串的键盘操作、语音识别或者点击输入法的备选词) 简单来说就是切换中文输入法时在打拼音时(此时input内还没有填入真正的内容),会首先触发compositionstart,然后每打一个拼音字母,触发compositionupdate,最后将输入好的中文填入input中时触发compositionend。 触发compositionstart时,文本框会填入 “虚拟文本”(待确认文本),同时触发input事件;在触发compositionend时,就是填入实际内容后(已确认文本) ,所以这里如果不想触发input事件的话就得设置一个bool变量来控制
上图中点击空格后才会填入实际的文本,输入英文或数字则没有这3个事件的触发
那么问题来了,为啥Element要设置这3个事件的处理函数呢?原因很简单,我们肯定不希望在输入拼音的过程中就直接触发input事件改变<el-input v-model="inputValue"></el-input>
中inputValue的值,而是希望输入完成后再改变,所以需要特殊处理,我们来看
handleComposition
的源码,注意这里只写了一个方法而不是3个,通过event.type来判断事件类型从而简化代码,可以借鉴
handleComposition(event) { if (event.type === 'compositionend') { this.isOnComposition = false; this.currentValue = this.valueBeforeComposition; this.valueBeforeComposition = null; this.handleInput(event); } else { const text = event.target.value; const lastCharacter = text[text.length - 1] || ''; this.isOnComposition = !isKorean(lastCharacter); if (this.isOnComposition && event.type === 'compositionstart') { this.valueBeforeComposition = text; } } }, 复制代码
这里首先在data中定义了一个bool变量 isOnComposition
,这个变量就是用来判断是否在打拼音的过程中,初始为false,当开始打拼音后,触发 compositionstart
事件,更新 isOnComposition
,通过 this.isOnComposition = !isKorean(lastCharacter)
来更新,这里的逻辑是判断输入的字符的最后一个是不是韩文,韩文通过正则表达式来判断,至于为啥要判断韩文的最后一个字符,不清楚~ 如果是中文,则 isOnComposition
为true,这里比较难理解的是后面这个if,当正在打拼音的过程中且是 compositionstart
事件时,则用一个 valueBeforeComposition
变量保存当前的文本,也就是保存此次打字前input中的文本内容,这个 valueBeforeComposition
的作用后面介绍,接下来看 if (event.type === 'compositionend')
中的内容,当打完拼音后,触发 compositionend
,此时设置 isOnComposition
为false表明打字完成,然后注意这里会手动触发一个 this.handleInput(event)
(handleInput就是input上绑定的v-on:input),这是因为最后输入完成时, compositionend
会在 input
事件后触发,此时 isOnComposition
还是true,无法触发下面handleInput中的emit将新的input的value传递给父组件,所以这里需要手动调用一次handleInput,这里请仔细理解!
handleInput(event) { const value = event.target.value; this.setCurrentValue(value); if (this.isOnComposition) return; this.$emit('input', value); }, 复制代码
handleInput中当 isOnComposition
为true时表明正在打拼音输入,则不触发emit事件,这是合理且正常的
可清空的实现
<el-input>
中如果添加了 clearable
属性则输入文字后会出现一个叉的图标,点击后input内容清空,如下图
先看html结构,下面是后置内容的html代码
<!-- 后置内容 --> <span class="el-input__suffix" v-if="$slots.suffix || suffixIcon || showClear || validateState && needStatusIcon"> <span class="el-input__suffix-inner"> <template v-if="!showClear"> <slot name="suffix"></slot> <i class="el-input__icon" v-if="suffixIcon" :class="suffixIcon"> </i> </template> <i v-else class="el-input__icon el-icon-circle-close el-input__clear" @click="clear" ></i> </span> <i class="el-input__icon" v-if="validateState" :class="['el-input__validateIcon', validateIcon]"> </i> </span> 复制代码
中间这段 <i>
就是清空按钮,它是一个i标签,有一个click事件,前面通过 showClear
来判断是否需要显示清空按钮,逻辑如下
showClear() { return this.clearable && !this.disabled && !this.readonly && this.currentValue !== '' && (this.focused || this.hovering); } 复制代码
这个计算属性第一步得看用户是否添加了显示清空按钮的属性,如果没有则不显示,如果有则继续判断,在非禁用且非只读状态下才且当前input的value不是空且该input获得焦点或者鼠标移动上去才显示,条件略多啊
然后看clear清空这个方法
clear() { this.$emit('input', ''); this.$emit('change', ''); this.$emit('clear'); this.setCurrentValue(''); this.focus(); } 复制代码
居然有5句话,但都不能少,第一个emit是通知父组件自己的value值变成了空,从而更新 <el-input v-model="v">
中的v这个data为空,第二句emit触发了父组件的change事件,这样在 <el-input v-model="v" @change="inputChange">
中的inputChange中就能监听到该事件了,第3个emit触发父组件的@clear方法,让父组件知道自己已经清空了,第四句话更新自己的currentValue为空,第五局让input获得焦点便于输入内容
textarea高度自适应的实现
这个就比较难了,这里只简单分析其原理,原生的textarea随着内容增多则会出现滚动条
而Element的处理却能够让其自适应高度,也就是不出现滚动条
核心原理是在textarea的input事件中进行逻辑判断,每触发一次input就判断一次,具体在下面函数中进行处理
function calcTextareaHeight(){ ... let height = hiddenTextarea.scrollHeight; const result = {}; ... result.height = `${ height }px`; return result } 复制代码
这里让height等于scrollHeight,也就是滚动条卷去的高度,这里就将height变大了,然后返回该height并绑定到input的style中从而动态改变textarea的height,具体代码很复杂,还要处理最大最小高度等,参考github
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Element源码分析系列7-InputNumber(数字输入框)
- PHP 对输入变量名的自动转换的问题与源码分析
- iOS个人中心渐变动画、微信对话框、标签选择器、自定义导航栏、短信验证输入框等源码
- Android输入系统(一)输入事件传递流程和InputManagerService的诞生
- Android输入系统(四)输入事件是如何分发到目标窗口的?
- Android输入系统(二)IMS的启动过程和输入事件的处理
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。