本集定位:
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: 連接描述