input 输入框组件

源码:

<template><div :class="[type === 'textarea' ? 'el-textarea' : 'el-input',inputSize ? 'el-input--'   inputSize : '',{'is-disabled': inputDisabled,'el-input-group': $slots.prepend || $slots.append,'el-input-group--append': $slots.append,'el-input-group--prepend': $slots.prepend,'el-input--prefix': $slots.prefix || prefixIcon,'el-input--suffix': $slots.suffix || suffixIcon || clearable}]"@mouseenter="hovering = true"@mouseleave="hovering = false"><!--当type的值不等于textarea时--><template v-if="type !== 'textarea'"><!-- 前置元素 --><div class="el-input-group__prepend" v-if="$slots.prepend"><slot name="prepend"></slot></div><!--核心部分:输入框--><input:tabindex="tabindex"v-if="type !== 'textarea'"class="el-input__inner"v-bind="$attrs":type="type":disabled="inputDisabled":readonly="readonly":autocomplete="autoComplete || autocomplete":value="currentValue"ref="input"@compositionstart="handleComposition"@compositionupdate="handleComposition"@compositionend="handleComposition"@input="handleInput"@focus="handleFocus"@blur="handleBlur"@change="handleChange":aria-label="label"><!-- input框内的头部的内容 --><span class="el-input__prefix" v-if="$slots.prefix || prefixIcon"><slot name="prefix"></slot><!--prefixIcon头部图标存在时,显示i标签--><i class="el-input__icon" v-if="prefixIcon" :class="prefixIcon"></i></span><!-- input框内的尾部的内容 --><span class="el-input__suffix" v-if="$slots.suffix || suffixIcon || showClear || validateState && needStatusIcon"><span class="el-input__suffix-inner"><!--showClear为false时,显示尾部图标--><template v-if="!showClear"><slot name="suffix"></slot><i class="el-input__icon" v-if="suffixIcon" :class="suffixIcon"></i></template><!--showClear为true时,显示清空图标--><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><!-- 后置元素 --><div class="el-input-group__append" v-if="$slots.append"><slot name="append"></slot></div></template><!--当type的值等于textarea时--><textareav-else:tabindex="tabindex"class="el-textarea__inner":value="currentValue"@compositionstart="handleComposition"@compositionupdate="handleComposition"@compositionend="handleComposition"@input="handleInput"ref="textarea"v-bind="$attrs":disabled="inputDisabled":readonly="readonly":autocomplete="autoComplete || autocomplete":style="textareaStyle"@focus="handleFocus"@blur="handleBlur"@change="handleChange":aria-label="label"></textarea></div>
</template>
<script>import emitter from 'element-ui/src/mixins/emitter';import Migrating from 'element-ui/src/mixins/migrating';import calcTextareaHeight from './calcTextareaHeight';import merge from 'element-ui/src/utils/merge';import { isKorean } from 'element-ui/src/utils/shared';export default {name: 'ElInput',componentName: 'ElInput',mixins: [emitter, Migrating],inheritAttrs: false,inject: {elForm: {default: ''},elFormItem: {default: ''}},data() {return {currentValue: this.value === undefined || this.value === null? '': this.value,textareaCalcStyle: {},hovering: false,focused: false,isOnComposition: false,valueBeforeComposition: null};},props: {value: [String, Number], //绑定值size: String, //输入框尺寸,只在type!="textarea" 时有效resize: String, //控制是否能被用户缩放form: String,disabled: Boolean, //禁用readonly: Boolean,type: {  //类型texttextarea和其他原生input的type值type: String,default: 'text'},autosize: { //自适应内容高度,只对 type="textarea" 有效,可传入对象,如,{ minRows: 2, maxRows: 6 }type: [Boolean, Object],default: false},autocomplete: {type: String,default: 'off'},/** @Deprecated in next major version */autoComplete: {type: String,validator(val) {process.env.NODE_ENV !== 'production' &&console.warn('[Element Warn][Input]\'auto-complete\' property will be deprecated in next major version. please use \'autocomplete\' instead.');return true;}},validateEvent: { //输入时是否触发表单的校验type: Boolean,default: true},suffixIcon: String, //输入框尾部图标prefixIcon: String, //输入框头部图标label: String, //输入框关联的label文字clearable: { //是否可清空type: Boolean,default: false},tabindex: String //输入框的tabindex},computed: {_elFormItemSize() {return (this.elFormItem || {}).elFormItemSize;},//校验状态validateState() {return this.elFormItem ? this.elFormItem.validateState : '';},needStatusIcon() {return this.elForm ? this.elForm.statusIcon : false;},validateIcon() {return {validating: 'el-icon-loading',success: 'el-icon-circle-check',error: 'el-icon-circle-close'}[this.validateState];},//textarea的样式textareaStyle() {return merge({}, this.textareaCalcStyle, { resize: this.resize });},//输入框尺寸,只在 type!="textarea" 时有效inputSize() {return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;},//input是否被禁用inputDisabled() {return this.disabled || (this.elForm || {}).disabled;},//是否显示清空按钮showClear() {// clearable属性为true,即用户设置了显示清空按钮的属性;并且在非禁用且非只读状态下才且当前input的value不是空且该input获得焦点或者鼠标移动上去才显示return this.clearable &&!this.inputDisabled &&!this.readonly &&this.currentValue !== '' &&(this.focused || this.hovering);}},watch: {value(val, oldValue) {this.setCurrentValue(val);}},methods: {focus() {(this.$refs.input || this.$refs.textarea).focus();},blur() {(this.$refs.input || this.$refs.textarea).blur();},getMigratingConfig() {return {props: {'icon': 'icon is removed, use suffix-icon / prefix-icon instead.','on-icon-click': 'on-icon-click is removed.'},events: {'click': 'click is removed.'}};},handleBlur(event) {this.focused = false;this.$emit('blur', event);if (this.validateEvent) {this.dispatch('ElFormItem', 'el.form.blur', [this.currentValue]);}},select() {(this.$refs.input || this.$refs.textarea).select();},resizeTextarea() {if (this.$isServer) return;//autosize自适应内容高度,只对 type="textarea" 有效,可传入对象,如,{ minRows: 2, maxRows: 6 }const { autosize, type } = this;if (type !== 'textarea') return;//如果没设置自适应内容高度if (!autosize) {this.textareaCalcStyle = { //高度取文本框的最小高度minHeight: calcTextareaHeight(this.$refs.textarea).minHeight};return;}const minRows = autosize.minRows;const maxRows = autosize.maxRows;//如果设置了minRows和maxRows需要计算文本框的高度this.textareaCalcStyle = calcTextareaHeight(this.$refs.textarea, minRows, maxRows);},handleFocus(event) {this.focused = true;this.$emit('focus', event);},handleComposition(event) {// 如果中文输入已完成if (event.type === 'compositionend') {//  isOnComposition设置为falsethis.isOnComposition = false;this.currentValue = this.valueBeforeComposition;this.valueBeforeComposition = null;//触发input事件,因为input事件是在compositionend事件之后触发,这时输入未完成,不会将值传给父组件,所以需要再调一次input方法this.handleInput(event);} else {  //如果中文输入未完成const text = event.target.value;const lastCharacter = text[text.length - 1] || '';//isOnComposition用来判断是否在输入拼音的过程中this.isOnComposition = !isKorean(lastCharacter);if (this.isOnComposition && event.type === 'compositionstart') {//  输入框中输入的值赋给valueBeforeCompositionthis.valueBeforeComposition = text;}}},handleInput(event) {const value = event.target.value;//设置当前值this.setCurrentValue(value);//如果还在输入中,将不会把值传给父组件if (this.isOnComposition) return;//输入完成时,isOnComposition为false,将值传递给父组件this.$emit('input', value);},handleChange(event) {this.$emit('change', event.target.value);},setCurrentValue(value) {// 输入中,直接返回if (this.isOnComposition && value === this.valueBeforeComposition) return;this.currentValue = value;if (this.isOnComposition) return;//输入完成,设置文本框的高度this.$nextTick(this.resizeTextarea);if (this.validateEvent && this.currentValue === this.value) {this.dispatch('ElFormItem', 'el.form.change', [value]);}},calcIconOffset(place) {let elList = [].slice.call(this.$el.querySelectorAll(`.el-input__${place}`) || []);if (!elList.length) return;let el = null;for (let i = 0; i < elList.length; i  ) {if (elList[i].parentNode === this.$el) {el = elList[i];break;}}if (!el) return;const pendantMap = {suffix: 'append',prefix: 'prepend'};const pendant = pendantMap[place];if (this.$slots[pendant]) {el.style.transform = `translateX(${place === 'suffix' ? '-' : ''}${this.$el.querySelector(`.el-input-group__${pendant}`).offsetWidth}px)`;} else {el.removeAttribute('style');}},updateIconOffset() {this.calcIconOffset('prefix');this.calcIconOffset('suffix');},//清空事件clear() {//父组件的value值变成了空,更新父组件中v-model的值this.$emit('input', '');//触发了父组件的change事件,父组件中就可以监听到该事件this.$emit('change', '');//触发了父组件的clear事件this.$emit('clear');//更新当前的currentValue的值this.setCurrentValue('');}},created() {this.$on('inputSelect', this.select);},mounted() {this.resizeTextarea();this.updateIconOffset();},updated() {this.$nextTick(this.updateIconOffset);}};
</script>

如下图所示:

(2)核心部分 input 输入框

<input:tabindex="tabindex"v-if="type !== 'textarea'"class="el-input__inner"v-bind="$attrs":type="type":disabled="inputDisabled":readonly="readonly":autocomplete="autoComplete || autocomplete":value="currentValue"ref="input"@compositionstart="handleComposition"@compositionupdate="handleComposition"@compositionend="handleComposition"@input="handleInput"@focus="handleFocus"@blur="handleBlur"@change="handleChange":aria-label="label">

1、 :tabindex="tabindex" 是控制tab键按下后的访问顺序,由用户传入tabindex;如果设置为负数则无法通过tab键访问,设置为0则是在最后访问。

2、 v-bind="$attrs" 为了简化父组件向子组件传值,props没有注册的属性,可以通过$attrs来取。

3、inputDisabled :返回当前input是否被禁用;readonly:input的原生属性,是否是只读状态;

4、 原生方法compositionstart、compositionupdate、compositionend

compositionstart 官方解释 : 触发于一段文字的输入之前(类似于 keydown 事件,但是该事件仅在若干可见字符的输入之前,而这些可见字符的输入可能需要一连串的键盘操作、语音识别或者点击输入法的备选词),通俗点,假如我们要输入一段中文,当我们按下第一个字母的时候触发 。
compositionupdate在我们中文开始输入到结束完成的每一次keyup触发。
compositionend则在我们完成当前中文的输入触发 。

这三个事件主要解决中文输入的响应问题,从compositionstart触发开始,意味着中文输入的开始且还没完成,所以此时我们不需要做出响应,在compositionend触发时,表示中文输入完成,这时我们可以做相应事件的处理。

 handleComposition(event) {// 如果中文输入已完成if (event.type === 'compositionend') {//  isOnComposition设置为falsethis.isOnComposition = false;this.currentValue = this.valueBeforeComposition;this.valueBeforeComposition = null;//触发input事件,因为input事件是在compositionend事件之后触发,这时输入未完成,不会将值传给父组件,所以需要再调一次input方法this.handleInput(event);} else {  //如果中文输入未完成const text = event.target.value;const lastCharacter = text[text.length - 1] || '';//isOnComposition用来判断是否在输入拼音的过程中this.isOnComposition = !isKorean(lastCharacter);if (this.isOnComposition && event.type === 'compositionstart') {//  输入框中输入的值赋给valueBeforeCompositionthis.valueBeforeComposition = text;}}},handleInput(event) {const value = event.target.value;//设置当前值this.setCurrentValue(value);//如果还在输入中,将不会把值传给父组件if (this.isOnComposition) return;//输入完成时,isOnComposition为false,将值传递给父组件this.$emit('input', value);},

(3)calcTextareaHeight.js使用来计算文本框的高度

//原理:让height等于scrollHeight,也就是滚动条卷去的高度,这里就将height变大了,然后返回该height并绑定到input的style中从而动态改变textarea的height
let hiddenTextarea;
//存储隐藏时候的css样式的
const HIDDEN_STYLE = `height:0 !important;visibility:hidden !important;overflow:hidden !important;position:absolute !important;z-index:-1000 !important;top:0 !important;right:0 !important
`;
//用来存储要查询的样式名
const CONTEXT_STYLE = ['letter-spacing','line-height','padding-top','padding-bottom','font-family','font-weight','font-size','text-rendering','text-transform','width','text-indent','padding-left','padding-right','border-width','box-sizing'
];function calculateNodeStyling(targetElement) {// 获取目标元素计算后的样式,即实际渲染的样式const style = window.getComputedStyle(targetElement);// getPropertyValue方法返回指定的 CSS 属性的值;这里返回box-sizing属性的值const boxSizing = style.getPropertyValue('box-sizing');// padding-bottom和padding-top值之和const paddingSize = (parseFloat(style.getPropertyValue('padding-bottom'))  parseFloat(style.getPropertyValue('padding-top')));// border-bottom-width和border-top-width值之和const borderSize = (parseFloat(style.getPropertyValue('border-bottom-width'))  parseFloat(style.getPropertyValue('border-top-width')));// 其他属性以及对应的值const contextStyle = CONTEXT_STYLE.map(name => `${name}:${style.getPropertyValue(name)}`).join(';');return { contextStyle, paddingSize, borderSize, boxSizing };
}export default function calcTextareaHeight(targetElement, //目标元素minRows = 1, //最小行数maxRows = null //最大行数
) {// 创建一个隐藏的文本域if (!hiddenTextarea) {hiddenTextarea = document.createElement('textarea');document.body.appendChild(hiddenTextarea);}//获取目标元素的样式let {paddingSize,borderSize,boxSizing,contextStyle} = calculateNodeStyling(targetElement);//设置对应的样式属性hiddenTextarea.setAttribute('style', `${contextStyle};${HIDDEN_STYLE}`);hiddenTextarea.value = targetElement.value || targetElement.placeholder || '';// 获取滚动高度let height = hiddenTextarea.scrollHeight;const result = {};if (boxSizing === 'border-box') {// 如果是 border-box,高度需加上边框height = height   borderSize;} else if (boxSizing === 'content-box') {// 如果是 content-box,高度需减去上下内边距height = height - paddingSize;}// 计算单行高度,先清空内容hiddenTextarea.value = '';// 再用滚动高度减去上下内边距let singleRowHeight = hiddenTextarea.scrollHeight - paddingSize;if (minRows !== null) {  // 如果参数传递了 minRows// 最少的高度=单行的高度*行数let minHeight = singleRowHeight * minRows;if (boxSizing === 'border-box') {// 如果是 border-box,还得加上上下内边距和上下边框的宽度minHeight = minHeight   paddingSize   borderSize;}// 高度取二者最大值height = Math.max(minHeight, height);result.minHeight = `${ minHeight }px`;}if (maxRows !== null) {let maxHeight = singleRowHeight * maxRows;if (boxSizing === 'border-box') {maxHeight = maxHeight   paddingSize   borderSize;}height = Math.min(maxHeight, height);}result.height = `${ height }px`;hiddenTextarea.parentNode && hiddenTextarea.parentNode.removeChild(hiddenTextarea);hiddenTextarea = null;return result;
};

参考博文:https://www.jianshu.com/p/74ba49507fe6
https://juejin.im/post/5b7d18e46fb9a01a12502616

更多专业前端知识,请上 【猿2048】www.mk2048.com

element-ui input组件源码分析整理笔记(六)相关推荐

  1. element-ui button组件 radio组件源码分析整理笔记(一)

    Button组件 button.vue <template><buttonclass="el-button"@click="handleClick&qu ...

  2. Element UI table组件源码分析

    本文章从如下图所示的最基本的table入手,分析table组件源代码.本人已经对table组件原来的源码进行削减,源码点击这里下载.本文只对重要的代码片段进行讲解,推荐下载代码把项目运行起来,跟着文章 ...

  3. Ui学习笔记---EasyUI的EasyLoader组件源码分析

    Ui学习笔记---EasyUI的EasyLoader组件源码分析 技术qq交流群:JavaDream:251572072   1.问题1:为什么只使用了dialog却加载了那么多的js   http: ...

  4. Framework学习之路(一)—— UI绘制深入源码分析

    Framework学习之路(一)-- UI绘制深入源码分析 本篇为笔者对Android SDK 33版本的UI绘制入口进行追踪的过程,主要作笔记作用.由于笔者经验尚浅,水平也有限,所以会存在很多不足的 ...

  5. Retrofit源码分析实践(六)【Retrofit 多BaseUrl问题解决】

    Retrofit源码分析&实践系列文章目录 Retrofit源码分析&实践(一)[从使用入手分析源码] Retrofit源码分析&实践(二)[Retrofit 免费的api测试 ...

  6. 【转】ABP源码分析三十六:ABP.Web.Api

    这里的内容和ABP 动态webapi没有关系.除了动态webapi,ABP必然是支持使用传统的webApi.ABP.Web.Api模块中实现了一些同意的基础功能,以方便我们创建和使用asp.net w ...

  7. 【转】ABP源码分析四十六:ABP ZERO中的Ldap模块

    通过AD作为用户认证的数据源.整个管理用户认证逻辑就在LdapAuthenticationSource类中实现. LdapSettingProvider:定义LDAP的setting和提供Defaut ...

  8. 【转】ABP源码分析二十六:核心框架中的一些其他功能

    本文是ABP核心项目源码分析的最后一篇,介绍一些前面遗漏的功能 AbpSession AbpSession: 目前这个和CLR的Session没有什么直接的联系.当然可以自定义的去实现IAbpSess ...

  9. ABP源码分析三十六:ABP.Web.Api

    这里的内容和ABP 动态webapi没有关系.除了动态webapi,ABP必然是支持使用传统的webApi.ABP.Web.Api模块中实现了一些同意的基础功能,以方便我们创建和使用asp.net w ...

最新文章

  1. 5G 产业链重要投资节点
  2. 完成users中的models
  3. 零售商的“基因改造”浪潮
  4. 设置DataGrid自动生成列的宽度
  5. 为什么使用Binder而不是其他IPC机制
  6. 直入灵魂的Python教学:《看动漫学Python》让学习不再枯燥
  7. Java发送form-data请求实现文件上传
  8. 攻克难题最忌讳的就是投机取巧自作聪明
  9. oracle中常用的方法,oracle常用方法
  10. 在Xcode编译的时候,报这个错误"library not found for -
  11. RNA剪接体 Spliceosome | 冷冻电镜 | 结构生物学
  12. processing软件使用python_Python processing学习
  13. Mac格式化fat32格式
  14. C# 获取Windows系统ICON图标的四种方式-可提取各种文件夹、文件等等图标
  15. picsart下载_PicsArt下载-PicsArt 安卓版v15.9.53-PC6安卓网
  16. 水星路由器wan口ip显示0_wan口状态ip地址为0.0.0.0
  17. 酒店无线wifi覆盖方案
  18. MATLAB调整为护眼模式
  19. 十分钟学会 web 开发利器 tornado
  20. 模型预测控制(MPC)的简单实现 — Matlab

热门文章

  1. python练习6——基础训练(mm追mhc)
  2. 伟大的淘宝IP库的API接口竟然提示503挂掉了
  3. 查询 (Tcode)跳转ID方法:(SET PARAMETER ID)
  4. 更多有效反链推广 增加反向链接十个方法
  5. 培训班学java学到什么程度可以出去工作了?
  6. 英语阅读进化 晾粥进化
  7. Linux Shell用IP反查网卡名称
  8. 计算机基础知识考题及答案,计算机基础知识试题及答案(一)
  9. Linux ALSA驱动框架(六)--ASoC架构中的Platfrom
  10. c++调用偏僻数据库db2