vue移動(dòng)助手實(shí)踐(二)——用vue指令實(shí)現(xiàn)插件并完成一個(gè)修改頭像功能

(By: Kath & kimmy)

最近在做的一個(gè)幾月vue的移動(dòng)端小demo,其中有一塊是實(shí)現(xiàn)各個(gè)頁面的統(tǒng)一換膚功能的。想著寫一篇文章,來寫一寫實(shí)現(xiàn)過程中遇到的一些問題。

項(xiàng)目在線demo

項(xiàng)目在線演示demo(切換到移動(dòng)端調(diào)試模式哦)

項(xiàng)目github地址

項(xiàng)目github地址

demo里有這么一個(gè)較隱蔽的修改頭像操作

修改頭像

正常的上傳頭像都帶選取裁切功能,這里先實(shí)現(xiàn)一張完整圖片的縮放和居中顯示,下個(gè)迭代開發(fā)再加入自定義選取框吧,
主要介紹的是下面兩點(diǎn)實(shí)現(xiàn)

1. 用transition 實(shí)現(xiàn)無縫過渡

2. 用directive (vue 指令)實(shí)現(xiàn)圖片的按寬高比縮放和居中顯示

一 用transition 實(shí)現(xiàn)無縫過渡

Kath 說為什么我做起來好像很好看樣子,果然年輕人都是喜歡特效的。
transition 在項(xiàng)目里面一般多少有人用到,主要用于實(shí)現(xiàn)一些動(dòng)態(tài)交互效果,它的出現(xiàn)解決了部分vue 在動(dòng)畫方面的薄弱——我們?nèi)耘f可以通過數(shù)據(jù)驅(qū)動(dòng)的形式,用v-show 和 v-if 去控制我們想要的效果,避免過多的dom操作。
我用transition實(shí)現(xiàn)的是一個(gè)入場(chǎng)離場(chǎng)的效果,home頁和修改頭像的info頁其實(shí)是兩個(gè)不同頁面,通過路由跳轉(zhuǎn),為了制造無縫的效果,我在兩個(gè)頁面都保留了頭像圖片這一個(gè)相同元素,制造了兩個(gè)頁面相關(guān)的假象,
所以實(shí)際的實(shí)現(xiàn)其實(shí)是


別人的出現(xiàn)效果
  1. 點(diǎn)擊home 頁頭像, 路由跳轉(zhuǎn)到info頁, 觸發(fā)info頁入場(chǎng)transition, 使圖片從起始位置,即home 頁頭像所在位置,過渡到當(dāng)前頁面的實(shí)際位置。 觸發(fā)info頁入場(chǎng)的操作,通過定義一個(gè)appear Boolean變量控制,用于v-show。而文字上升的效果,同樣是在進(jìn)場(chǎng)時(shí)候觸發(fā)transition, 而進(jìn)場(chǎng)動(dòng)畫的交互效果, 參考了ant design的設(shè)計(jì)風(fēng)格,看了人家那些列表元素進(jìn)場(chǎng)效果是怎樣的···
<!-- 頭像區(qū)域 -->
  <transition name="slide">
    <div class="head-field" v-show="appear">
      <span class="head-field-pic">
         <span class="img-hover" @click.stop="uploadHeadImg">
            ![](userinfo.headUrl)
          </span>
        </span>
    </div>
  </transition>
···
data () {
    return {
      appear: false  // 控制進(jìn)場(chǎng)
    }
  },
  mounted () {
    this.$nextTick(() => {
      this.appear = true
    })
  },
···
<style lang="scss" rel="stylesheet/scss">
.slide-enter-active,
    .slide-leave-active {
      transform: translateY(0);
      transition: transform 1s;
    }
    .slide-enter,
    .slide-leave-to/* .fade-leave-active in below version 2.1.8 */
    {
      transform: translateY(-50px);
    }
</style>

關(guān)于文字效果的實(shí)現(xiàn),這里又可以普及小scss的小眾用法,我的實(shí)現(xiàn)看起來是這樣的

<div class="info-field">
      <transition name="slide-1">
        <p v-show="appear">K.K</p>
      </transition>
      <transition name="slide-2">
        <p v-show="appear">wanna to be a Brilliant gentle</p>
      </transition>
      <transition name="slide-3">
        <p v-show="appear">And a pretty girl</p>
      </transition>
  </div>
···
<style lang="scss" rel="stylesheet/scss">
@for $i from 1 to 4 {
      .slide-#{$i}-enter-active {
        transform: translateY(0);
        opacity: 1;
        transition: transform 1s, opacity 1s;
        transition-delay: ($i - 1s) / 5;
      }
      .slide-#{$i}-leave-active {
        transform: translateY(0);
        opacity: 1;
        transition: transform .5s, opacity .5s;
      }
      .slide-#{$i}-enter,
      .slide-#{$i}-leave-to {
        opacity: 0;
        transform: translateY(50px);
      }
    }
</style>

太多個(gè)transition以及還沒循環(huán)的頁面模板還要優(yōu)化,這個(gè)還在考慮一個(gè)好的實(shí)現(xiàn),想說的是transition-delay: ($i - 1s) / 5; 這句看起來就很優(yōu)雅有沒有, 主要功能是給他們進(jìn)場(chǎng)時(shí)候打了個(gè)時(shí)間差,通過變量加上一些修正就可以制造契合優(yōu)雅的數(shù)列,在css里面寫表達(dá)式還是有種成就感的···

2. 離場(chǎng)

離場(chǎng)的效果和入場(chǎng)如出一轍,樣式交互以及在上面定義好了,主要我們要考慮的是轉(zhuǎn)場(chǎng)需要一點(diǎn)時(shí)間去完成這系列出場(chǎng)動(dòng)畫,(否則下一個(gè)進(jìn)來的頁面就會(huì)立刻出現(xiàn),動(dòng)畫會(huì)中止或覆蓋)

beforeRouteLeave (to, from, next) {
    this.appear = false
    setTimeout(() => {
      next()
    }, 800)
  },

二 用directive 指令實(shí)現(xiàn)圖片縮放居中顯示

和jquery有很多插件一樣,vue 也有很多逐漸完善的插件, 而directive 可以說是vue插件開發(fā)里面的很重要的一個(gè)部分。
和我們寫組件不一樣,我們的組件大多針對(duì)一個(gè)功能或或一個(gè)業(yè)務(wù)塊,實(shí)現(xiàn)完整的功能。然后插件我理解為比較嵌入式的,針對(duì)多是全局的通用的,輔助性質(zhì)功能。比如在一張圖片綁定一個(gè)v-preview 指令,實(shí)現(xiàn)圖片預(yù)覽, 在一個(gè)div綁定指令,實(shí)現(xiàn)popover功能等。
觀察element.ui 源碼發(fā)現(xiàn)也有很多值得借鑒的東西,比如我在項(xiàng)目的指令里面加了clickoutside的功能,在對(duì)應(yīng)的元素綁定 v-myclickoutside, 用戶在點(diǎn)擊除該元素外的頁面其他地方都會(huì)觸發(fā)綁定事件。常用的場(chǎng)景就是我們自己寫下拉框,彈出框時(shí)候,點(diǎn)擊頁面外部會(huì)自動(dòng)收起下拉框,(換做以前我們得監(jiān)聽body點(diǎn)擊事件,可能還要解綁,一個(gè)元素寫一次綁定那種),具體實(shí)現(xiàn)可以參照項(xiàng)目代碼


點(diǎn)擊組件外部自動(dòng)收起

這里我說下對(duì)圖片綁定v-autofix, 實(shí)現(xiàn)圖片自動(dòng)壓縮居中顯示的功能, 來簡(jiǎn)述指令插件的開發(fā)過程

/**
// v-autofix指令
export default {
  install (Vue) {
    let handleImg = (el, binding, vnode) => {
      if (!el || !el.parentNode) {
        return
      }
      // console.log('carry', el, binding, el.parentNode)
      let img = new Image()
      let boxWidth = el.parentNode.offsetWidth
      img.onload = () => {
        // 以長(zhǎng)度小的邊為基準(zhǔn), 按比例縮放,然后偏移最長(zhǎng)邊和當(dāng)前邊框長(zhǎng)度差的一半
        if (img.width < img.height) {
          el.style.height = Math.floor(img.height / img.width * boxWidth) + 'px'
          el.style.width = boxWidth + 'px'
          el.style.marginTop = -(el.offsetHeight - boxWidth) / 2 + 'px'
        } else {
          el.style.width = Math.floor(img.width / img.height * boxWidth) + 'px'
          el.style.height = boxWidth + 'px'
          el.style.marginLeft = -(el.offsetWidth - boxWidth) / 2 + 'px'
        }
      }
      img.src = el.src
    }
    Vue.directive('autofix', {
      inserted (el, binding, vnode) {
        handleImg(el, binding, vnode)
      },
      update (el, binding, vnode) {
        handleImg(el, binding, vnode)
      },
      unbind (el) {
      }
    })
  }
}

1. directive

首先第一步,關(guān)于vue directive, 我們可以用directive這么注冊(cè)一個(gè)指令, 參照vue directive

// 注冊(cè)一個(gè)全局自定義指令 v-focus
Vue.directive('focus', {
  // 當(dāng)綁定元素插入到 DOM 中。
  inserted: function (el) {
    // 聚焦元素
    el.focus()
  }
})

其中, 我們可以綁定的鉤子函數(shù)有幾個(gè),他們的參數(shù)都為 el,binding,vnode,oldVnode等,先看官方描述,再看我的理解
bind:指令第一次綁定到元素時(shí)調(diào)用,可以定義一個(gè)在綁定時(shí)執(zhí)行一次的初始化動(dòng)作,和inserted區(qū)別是,這個(gè)過程發(fā)生在這個(gè)節(jié)點(diǎn)生成,但還沒有插入dom時(shí)候,所以你會(huì)發(fā)現(xiàn),你企圖在這個(gè)鉤子里面獲取到el.parentNode時(shí)候是失敗的。
inserted:被綁定元素插入父節(jié)點(diǎn)時(shí)調(diào)用,如果說我們希望我們的動(dòng)作只執(zhí)行一次,但又需要和其他節(jié)點(diǎn)關(guān)聯(lián)(如獲取父元素寬高,修改他們屬性值等),那么我們就應(yīng)該在inserted執(zhí)行我們的操作。
update:任何節(jié)點(diǎn)變化,屬性值變化等都會(huì)執(zhí)行該鉤子,所以可以作為一個(gè)監(jiān)聽事件,而且他有其他鉤子不具備的oldValue等參數(shù)值,方便我們判斷是否該變化需要執(zhí)行我們的操作。
unbind:只調(diào)用一次,指令與元素解綁時(shí)調(diào)用。

2. 了解我們的需求

我們需要的是這么個(gè)東西,在圖片上綁定一個(gè)v-autofix指令,當(dāng)這張圖片src變化后(我們獲取到上傳的圖片后,修改圖片src), 能自動(dòng)根據(jù)獲取的圖片的寬高,根據(jù)他們比例去壓縮成我們div的大小,

實(shí)際圖
效果圖

所以我們可以確定我們要觸發(fā)的時(shí)機(jī),一個(gè)是頁面加載時(shí)候,一個(gè)是src變化時(shí)候,所以我們可以確定用
bind/inserted 以及 update作為鉤子函數(shù)

  1. 理解各參數(shù)意義,實(shí)現(xiàn)邏輯
    bind/inserted 以及 update函數(shù)都提供了我們 el(綁定元素), binding對(duì)象等值,我們思考我們獲取圖片寬高的方法,實(shí)際上是等待image加載完畢,獲取img 寬高的過程,因此,我們可以通過以下實(shí)現(xiàn),獲取元素src,
    通過new image加載圖片,獲取對(duì)應(yīng)寬高
      let img = new Image()
      img.onload = () => {
      // get img.width
      // get img.height
      }
      img.src = el.src

緊接著,我們可以計(jì)算長(zhǎng)寬比,以最小的寬或高為準(zhǔn)縮放圖片

let img = new Image()
      img.onload = () => {
        // 以長(zhǎng)度小的邊為基準(zhǔn), 按比例縮放,然后偏移最長(zhǎng)邊和當(dāng)前邊框長(zhǎng)度差的一半
        if (img.width < img.height) {
          el.style.height = Math.floor(img.height / img.width * boxWidth) + 'px'
          el.style.width = boxWidth + 'px'
        } else {
          el.style.width = Math.floor(img.width / img.height * boxWidth) + 'px'
          el.style.height = boxWidth + 'px'
        }
      }
      img.src = el.src

最后一步居中顯示,這里我通過在圖片上層定義父元素,通過img的偏移長(zhǎng)寬差一半來實(shí)現(xiàn)居中效果

let handleImg = (el, binding, vnode) => {
      if (!el || !el.parentNode) {
        return
      }
      // console.log('carry', el, binding, el.parentNode)
      let img = new Image()
      let boxWidth = el.parentNode.offsetWidth
      img.onload = () => {
        // 以長(zhǎng)度小的邊為基準(zhǔn), 按比例縮放,然后偏移最長(zhǎng)邊和當(dāng)前邊框長(zhǎng)度差的一半
        if (img.width < img.height) {
          el.style.height = Math.floor(img.height / img.width * boxWidth) + 'px'
          el.style.width = boxWidth + 'px'
          el.style.marginTop = -(el.offsetHeight - boxWidth) / 2 + 'px'
        } else {
          el.style.width = Math.floor(img.width / img.height * boxWidth) + 'px'
          el.style.height = boxWidth + 'px'
          el.style.marginLeft = -(el.offsetWidth - boxWidth) / 2 + 'px'
        }
      }
      img.src = el.src
    }

值得注意的是,這里我需要獲取父元素寬度,所以前面說的,在bind過程獲取不到父元素,只能用inserted啦
這是個(gè)相對(duì)簡(jiǎn)單的指令應(yīng)用,目前也只用了el 的操作,還有更完善的實(shí)現(xiàn),就需要一起探討學(xué)習(xí)啦

最后一提,修改頭像的功能到這里差不多就沒什么好講了,只要做好顯示,剩下的工作,我只是把更新的圖片轉(zhuǎn)成base64存在localstorage里而已,多多指教。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 這篇筆記主要包含 Vue 2 不同于 Vue 1 或者特有的內(nèi)容,還有我對(duì)于 Vue 1.0 印象不深的內(nèi)容。關(guān)于...
    云之外閱讀 5,168評(píng)論 0 29
  • 轉(zhuǎn)載 :OpenDiggawesome-github-vue 是由OpenDigg整理并維護(hù)的Vue相關(guān)開源項(xiàng)目庫...
    果汁密碼閱讀 23,393評(píng)論 8 124
  • 1.安裝 可以簡(jiǎn)單地在頁面引入Vue.js作為獨(dú)立版本,Vue即被注冊(cè)為全局變量,可以在頁面使用了。 如果希望搭建...
    Awey閱讀 11,278評(píng)論 4 129
  • 雷哥回來的前一周是我噩夢(mèng)般的一周。各種事情,各種理不順,還給侯老一種不好的印象吧maybe. 我也很無奈??!然后就...
    Lynn歐尼閱讀 247評(píng)論 0 0
  • 效果圖奉上 一、關(guān)于布局: 一個(gè)Button、一個(gè)label,太簡(jiǎn)單了不寫了 二、需要用的知識(shí)點(diǎn)包括:使用AVAu...
    Codepgq閱讀 379評(píng)論 0 0

友情鏈接更多精彩內(nèi)容