本集定位:
input组件是交互的一大利器, 他与用户的交流最为密切, 因此奠基了他在组件界的重要地位.
textarea也算是一种input, 若是能够的话, 本集也会一块儿说完, 毕竟是一个类型的, 一块儿学完收获会很大.
古人云:"组件不封输入框,一到面试就发慌"css
一. v-model 简介
你们若是对 v-model这个指令的原理不熟悉, 建议去学习下vue源码或者看看相关的分析文章, 很重要的知识, 封装组件多了就会知道这个指令真是太棒了! 这里我就简单说一下他的规则.
1: 父级在组件上绑定了v-model时, 其实就是在往组件里面传递value变量.
2: 你的组件在props上定义value, 就能够取到值.
3: 每当组件里this.$emit("input",n)往外面发送事件的时候, 外面会把这个n值 赋值给value
4: 这么设计的缘由: 你在组件里面无权改变传入的值, 这个值你想改为什么值就要吐出去, 让外面改.vue
好了说了这么多开始实战吧!git
二. 基本结构
vue-cc-ui/src/components/Input/index.js
老套路, 统一导出为了适配vue.use的使用方式github
import Input from './main/input.vue' Input.install = function(Vue) { Vue.component(Input.name, Input); }; export default Input
vue-cc-ui/src/components/Input/main/input.vueweb
<template> <div class="cc-input"> <input type="text" class='cc-input__inner' :value="value" v-bind="$attrs" :placeholder="placeholder" @input="$emit('input',$event.target.value)"/> </div> </template>
props: { value: [String, Number], placeholder: [String, Number], type: { type: String, default: "text" } },
三. 丰富事件面试
<input :type="type" class='cc-input__inner' :value="value" v-bind="$attrs" :autofocus="autofocus" // 是否自动聚焦 :placeholder="placeholder" @blur="blur($event.target.value)" @input="$emit('input',$event.target.value)" // 这里有个小细节, 就是这个事件绑定了两个操做 // 不只触发聚焦事件, 还把变量focus设定为真 @focus="$emit('focus',$event.target.value);focus=true" @change="$emit('change',$event.target.value)" />
四. 各类状态vue-router
具体样式会在后面出来详细解释vuex
<input :type="type" :disabled="disabled" // 都是原生属性, 但要添加样式 :readonly="readonly" // 都是原生属性, 不用添加样式 :class="{ 'cc-input--input__disabled':disabled }" />
五. 为输入框添加状态, 并附上icon选项element-ui
<template> <div class="cc-input" :class="{ // 对每种状态给与相应的class 'cc-input__error':error, 'cc-input__normal':!disabled&&!normal, 'cc-input__abnormal':normal, 'cc-input__disabled':disabled, }" :style="{ // 输入框有悬停放大的效果, 这里能够调节放大的角度, 下面有图演示 'transform-origin':`${transformOrigin} 0` }"> <nav v-if="leftIcon" class="cc-input__prefix is-left" // 返回相应的点击事件 @click="$emit('clickLeftIcon')"> <ccIcon :name='leftIcon' :color='iconColor' // 这里图标也要置灰 :disabled='disabled' /> </nav> <input :type="type" class='cc-input__inner' :value="value" v-bind="$attrs" :disabled="disabled" :readonly="readonly" :autofocus="autofocus" :placeholder="placeholder" :class="{ 'cc-input--input__disabled':disabled }" @blur="blur($event.target.value)" @input="$emit('input',$event.target.value)" @focus="$emit('focus',$event.target.value);focus=true" @change="$emit('change',$event.target.value)" /> <nav v-if="icon&&!clear" class="cc-input__prefix is-right" @click="$emit('clickRightIcon')"> <ccIcon :name="clear?'cc-close':icon" :color='iconColor' :disabled='disabled' /> // 容许用户插入各类节点 <slot /> </nav> </div> </template>
效果图app
六. 清空按钮
如今的输入框基本都有这个清空按钮, 毕竟能够节省用的时间, 也算是个好功能,
当用户传入clear的时候会判断, 是否禁止修改, 框内是否有值, 是不是hover状态
hover事件放在父级上
<div class="cc-input" @mouseenter="hovering = true" @mouseleave="hovering = false">
<nav v-if="showClear" class="cc-input__clear" @click="clickClear"> <ccIcon name="cc-close" :disabled='disabled' /> // 这里是为了样式的统一 // 好比用户在右侧按钮写了不少文字 // 那么clear按钮很差定位, 因此才写了这个站位 <span style=" opacity: 0;"> <slot /> </span> </nav>
清除事件, 对外返回空就ok
clickClear() { this.$emit("input", ""); this.$emit("change", ""); },
判断是否显示
computed: { showClear() { if ( this.clear && // 开启功能 !this.disabled && // 不是禁用 !this.readonly && // 不是只读 this.value!== '' && // 不是空值 (this.hovering || this.focus) // 聚焦或者hover状态下 )return true; return false; } },
vue-cc-ui/src/style/Input.scss
// 引入老四样 @import './common/var.scss'; @import './common/extend.scss'; @import './common/mixin.scss'; @import './config/index.scss'; // 这里毕竟是两个月前写的组件, 命名方面不是很好, 接下来会统一改正 @include b(input) { cursor: pointer; position: relative; align-items: center; display: inline-flex; // 直接flex会独占一行 background-color: white; transition: all .3s; @include b(input__inner) { border: none; flex: 1; width: 100%; font-size: 1em; padding: 9px 16px; &:focus { outline: 0; } // 这样写对障碍阅读不是很友好 @include placeholder{ // placeholder设置颜色很头疼, 请看下面 color: $--color-input-placeholder; } }; @include b(input__prefix) { align-items: center; display: inline-flex; &:hover{transform: scale(1.1)} @include when(left) { padding-left:6px; } @include when(right) { padding-right:6px; } }; @include b(input__clear){ position: absolute; right: 24px; &:hover{ animation: size .5s infinite linear;} }; @include b(input--input__disabled){ @include commonShadow(disabled); }; @at-root { @include b(input__normal){ @include commonShadow($--color-black); &:hover { z-index: 6; transform: scale(1.2); } } @include b(input__error){ @include commonShadow(danger); } @include b(input__abnormal){ @include commonShadow($--color-black); } } }
element 这个处理作的也不错
@mixin placeholder { &::-webkit-input-placeholder { @content; } &::-moz-placeholder { @content; } &:-ms-input-placeholder { @content; } }
七. textarea 文本域
基本结构
<template> <div class="cc-input" ....> <template v-if="type !== 'textarea'"> <input :type="type" ..../> </template> <textarea v-else // 必须获取这个dom ref="textarea" class='cc-input__inner' :value="value" v-bind="$attrs" :disabled="disabled" :readonly="readonly" :autofocus="autofocus" :placeholder="placeholder" @blur="$emit('blur',$event.target.value)" @input="$emit('input',$event.target.value)" @focus="$emit('focus',$event.target.value)" @change="$emit('change',$event.target.value)" :style="{ width:rows, height:cols, ...textareaCalcStyle}" :class="{ 'cc-input--input__disabled':disabled, 'cc-input--input__autosize':autosize}" /> </div> </template>
针对textarea获取其真实高度进行高度的动态赋值;
我来讲说他的原理, 制做一个与textarea对象相同的元素, 获取他的滚动距离与高度, 计算出总的高度, 而后赋值给真正的textarea, 这里的亮点就是怎么作一个相同的dom, 由于用户可能给这个dom不一样的样式, 不一样的class, 各类各样的父级, 腹肌还会影响这个元素的样式;
// 我的建议, 这种生命周期函数都放在最底部, 而且要保持单一职责 mounted() { this.$nextTick(this.resizeTextarea); }
1: 判断是否是 autosize自动高度, 而且是组件autosize
2: 用户是否设置了最大高度与最小高度的限制
3: 这个函数只负责处理是否进行计算 calcTextareaHeight 负责计算.
resizeTextarea() { const { autosize, type } = this; if (type !== "autosize" || !autosize) return; const minRows = autosize.min; const maxRows = autosize.max; this.textareaCalcStyle = this.calcTextareaHeight( this.$refs.textarea, minRows, maxRows ); },
calcTextareaHeight
calcTextareaHeight(el, min, max) { // 也算是单例模式, 制做一个元素就好了 if (!window.hiddenTextarea) { window.hiddenTextarea = document.createElement("textarea"); document.body.appendChild(window.hiddenTextarea); } // 取得他的属性, 具体获取属性函数下面会讲 let [boxSizing, paddingSize, borderSize] = this.calculateNodeStyling(el); // 滚动距离 let height = window.hiddenTextarea.scrollHeight; // 是不是怪异盒模型, 进行分别的计算 if (boxSizing === "border-box") { height = height + borderSize; } else { height = height - paddingSize; } // 及时清理,让用户看不到这个元素 window.hiddenTextarea.parentNode && window.hiddenTextarea.parentNode.removeChild(window.hiddenTextarea); window.hiddenTextarea = null; if (min && height < min) height = min; else if (max && height > max) height = max; return { height: height + "px" }; }
calculateNodeStyling
calculateNodeStyling(el) { // 模拟元素经过值的输入模拟真正的元素 window.hiddenTextarea.value = this.value; const style = window.getComputedStyle(el); const boxSizing = style.getPropertyValue("box-sizing"); const paddingTop = style.getPropertyValue("padding-top"); const paddingBottom = style.getPropertyValue("padding-bottom"); const borderTopWidth = style.getPropertyValue("border-top-width"); const borderBottomWidth = style.getPropertyValue("border-bottom-width"); const contextStyle = this.CONTEXT_STYLE.map( name => `${name}:${style.getPropertyValue(name)}` ).join(";"); window.hiddenTextarea.setAttribute( "style", `${contextStyle};${this.HIDDEN_STYLE}` ); return [ boxSizing, parseInt(paddingBottom) + parseInt(paddingTop), parseInt(borderBottomWidth) + parseInt(borderTopWidth) ]; },
上面 用到的this.CONTEXT_STYLE数据是样式的列表
data() { return { focus: false, // 监听输入框的聚焦失焦 hovering: false, textareaCalcStyle: {}, CONTEXT_STYLE: [ "width", "font-size", "box-sizing", "line-height", "padding-top", "font-family", "font-weight", "text-indent", "border-width", "padding-left", "padding-right", "letter-spacing", "padding-bottom", "text-rendering", "text-transform" ] }; },
至此才把这个组件作完, 好辛苦
end
若是想作到面面俱到就没有简单的组件, element上的每一个组件都值得借鉴.
其实不少原理明白以后学习才能更快捷, 最近拿出时间与你们风向一下vue的实现原理, vue-router vuex等等的实现原理, 但愿能对你们对我本身都有帮助吧,, 只能说学海无涯悬崖勒马😁.
但愿你们一块儿进步, 实现自我价值!!
下一集准备聊聊 计数器
更多好玩的效果请关注我的博客: 连接描述
github: 连接描述