内容简介:本来不打算写输入框的分析,心想一个输入框能有多复杂,还能怎么封装,后来浏览了下源码,发现还是有很多自己不知道的知识点,于是打算还是写,下图就是一个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的宽度是自适应的,如下图
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的启动过程和输入事件的处理
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Game Programming Patterns
Robert Nystrom / Genever Benning / 2014-11-2 / USD 39.95
The biggest challenge facing many game programmers is completing their game. Most game projects fizzle out, overwhelmed by the complexity of their own code. Game Programming Patterns tackles that exac......一起来看看 《Game Programming Patterns》 这本书的介绍吧!