摘要:第八集从零开始实现输入框组件本集定位组件是交互的一大利器他与用户的交流最为密切所以奠定了他在组件界的重要地位也算是一种如果可以的话本集也会一起说完毕竟是一个类型的一起学完收获会很大古人云组件不封输入框,一到面试就发慌一简介大家如果对这个
第八集: 从零开始实现(输入框input,textarea组件)
本集定位:
input组件是交互的一大利器, 他与用户的交流最为密切, 所以奠定了他在组件界的重要地位.
textarea也算是一种input, 如果可以的话, 本集也会一起说完, 毕竟是一个类型的, 一起学完收获会很大.
古人云:"组件不封输入框,一到面试就发慌"
一. v-model 简介
大家如果对 v-model这个指令的原理不熟悉, 建议去学习下vue源码或者看看相关的分析文章, 很重要的知识, 封装组件多了就会知道这个指令真是太棒了! 这里我就简单说一下他的规则.
1: 父级在组件上绑定了v-model时, 其实就是在往组件里面传递value变量.
2: 你的组件在props上定义value, 就可以取到值.
3: 每当组件里this.$emit("input",n)往外面发送事件的时候, 外面会把这个n值 赋值给value
4: 这么设计的原因: 你在组件里面无权改变传入的值, 这个值你想改成什么值就要吐出去, 让外面改.
好了说了这么多开始实战吧!
二. 基本结构
vue-cc-ui/src/components/Input/index.js
老套路, 统一导出为了适配vue.use的使用方式
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.vue
type: 这个属性比较重要, 因为要通过它来区分input与textarea, 还可以为input指定number模式.
命名依然是bem
v-bind="$attrs" 解释下这个的意思, $attrs指的就是用户传进来的属性, 但是不包括我们组件内部用props接收的属性, 也不包括class style这种, 写它是为了用户可以传很多input原生的属性, 毕竟我们没必要把所有属性都做处理, 让组件保有原生功能.
placeholder这种模式基本也被现代抛弃了, 针对他也可以封装成一个具体的组件, 这个属性想调整属性实在是太困难了, 更别说我们现在还需要placeholder轮播,变色,点击等等效果.
vue 在行间写事件的时候, 事件对象会以$event的形式传给你使用, 其实从代码的角度来说, 是监控到你这里用了$event关键词,则把对应的参数赋值为事件对象.
props: { value: [String, Number], placeholder: [String, Number], type: { type: String, default: "text" } },
三. 丰富事件
输入框有很多种事件, 他们能给用户更好的体验性.
比如在手机端, 我们项目之前遇到的问题就是, 用户点击输入框的时候, 会弹出手机键盘, 但是弹出的键盘会把输入框顶上去, 某些型号的手机会出现, 就算输入完毕点击完成, 可是输入框还是被顶上去的状态, 后来我是借助blur 与 focus事件才兼容了这写手机
很多输入框也采取节流与防抖, 比如做搜索的相关模糊匹配
有些以搜索为主的页面, 需要自动聚焦
四. 各种状态
禁用状态, 置灰并且把鼠标变为禁止状态 (disabled)
只读, 并不置灰, 但是也不能改 (readonly)
具体样式会在后面出来详细解释
五. 为输入框添加状态, 并附上icon选项
很多输入框左右都要放个icon充充门面, 分为左侧与右侧icon
右侧icon允许输入文字, icon要有相应的点击效果
当组件为disabled状态的时候, icon也要相应的置灰
效果图
六. 清空按钮
现在的输入框基本都有这个清空按钮, 毕竟可以节省用的时间, 也算是个好功能,
当用户传入clear的时候会判断, 是否禁止修改, 框内是否有值, 是否是hover状态
hover事件放在父级上
清除事件, 对外返回空就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 文本域
基本结构
在用户type输入的是textarea时候开启
把上面的基础功能复制下来, 直接放上就可以用的
textareaCalcStyle: 来设置他的宽高, 毕竟他与input不同, 可能需要很大面积
用户可以设置最大高度与最小高度
难点: 如果用户选择了自动适应高度那就麻烦了, 这个组件没有提供原生的解决方案, 第一版我是采用获取其高度进行运算得出来的, 但是及特殊的情况会有bug, 最后参考了element-ui的实现方式, 这里也让我学习到了.
针对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等等的实现原理, 希望能对大家对我自己都有帮助吧,, 只能说学海无涯回头是岸文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/116169.html
摘要:第八集从零开始实现输入框组件本集定位组件是交互的一大利器他与用户的交流最为密切所以奠定了他在组件界的重要地位也算是一种如果可以的话本集也会一起说完毕竟是一个类型的一起学完收获会很大古人云组件不封输入框,一到面试就发慌一简介大家如果对这个 第八集: 从零开始实现(输入框input,textarea组件) 本集定位: input组件是交互的一大利器, 他与用户的交流最为密切, 所以奠...
摘要:第十集从零开始实现计数器组件本集定位听到计数器这个名字很多人是不是一瞬间没有什么印象毕竟这个组件用的比较少就是那种左边一个右边一个控制某些数量的时候才会用到比如我之前做的商城小程序只有下单页面的规格弹出框里面才有他的身影如果是涉及到处理商 第十集: 从零开始实现( 计数器组件 ) 本集定位: 听到计数器这个名字很多人是不是一瞬间没有什么印象, 毕竟这个组件用的比较少,就是那种左边...
摘要:第十集从零开始实现计数器组件本集定位听到计数器这个名字很多人是不是一瞬间没有什么印象毕竟这个组件用的比较少就是那种左边一个右边一个控制某些数量的时候才会用到比如我之前做的商城小程序只有下单页面的规格弹出框里面才有他的身影如果是涉及到处理商 第十集: 从零开始实现( 计数器组件 ) 本集定位: 听到计数器这个名字很多人是不是一瞬间没有什么印象, 毕竟这个组件用的比较少,就是那种左边...
摘要:第十一集从零开始实现切换组件本集定位我们先来聊聊切换的意义不管是手机还是屏幕的大小是有限的人眼睛看到的范围也是有限的人们看信息的时候并不喜欢跳转这种操作或是我们要查某个知识点进入网站之后看了几眼没有需要的相关信息也就理所当然的退出去继续搜索 第十一集: 从零开始实现( tab切换组件 ) 本集定位: 我们先来聊聊 tab 切换的意义, 不管是手机还是pc, 屏幕的大小是有限的,...
摘要:第一集从零开始实现环境的搭建工程定位本套工程定位在端针对的组件库名字的由来是我从年养到现在的一直大金毛是我的吉祥物原因本人上一份工作参与了大型的保险公司后台管理系统的搭建对的端框架有过一定的了解感受到了他们真的很强大同时也存在少许的不足其实 第一集: 从零开始实现(环境的搭建) 工程定位: 本套工程, 定位在pc端针对vue的ui组件库 名字的由来 cc是我从2015年养到现在的...
阅读 2672·2021-09-24 09:47
阅读 4295·2021-08-27 13:10
阅读 2956·2019-08-30 15:44
阅读 1243·2019-08-29 12:56
阅读 2576·2019-08-28 18:07
阅读 2589·2019-08-26 14:05
阅读 2504·2019-08-26 13:41
阅读 1245·2019-08-26 13:33