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

(By: Kath & kimmy)

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

項(xiàng)目在線demo

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

項(xiàng)目github地址

項(xiàng)目github地址

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

修改頭像

正常的上傳頭像都帶選取裁切功能,這里先實(shí)現(xiàn)一張完整圖片的縮放和居中顯示,下個迭代開發(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)一些動態(tài)交互效果,它的出現(xiàn)解決了部分vue 在動畫方面的薄弱——我們?nèi)耘f可以通過數(shù)據(jù)驅(qū)動的形式,用v-show 和 v-if 去控制我們想要的效果,避免過多的dom操作。
我用transition實(shí)現(xiàn)的是一個入場離場的效果,home頁和修改頭像的info頁其實(shí)是兩個不同頁面,通過路由跳轉(zhuǎn),為了制造無縫的效果,我在兩個頁面都保留了頭像圖片這一個相同元素,制造了兩個頁面相關(guān)的假象,
所以實(shí)際的實(shí)現(xiàn)其實(shí)是


別人的出現(xiàn)效果
  1. 點(diǎn)擊home 頁頭像, 路由跳轉(zhuǎn)到info頁, 觸發(fā)info頁入場transition, 使圖片從起始位置,即home 頁頭像所在位置,過渡到當(dāng)前頁面的實(shí)際位置。 觸發(fā)info頁入場的操作,通過定義一個appear Boolean變量控制,用于v-show。而文字上升的效果,同樣是在進(jìn)場時(shí)候觸發(fā)transition, 而進(jìn)場動畫的交互效果, 參考了ant design的設(shè)計(jì)風(fēng)格,看了人家那些列表元素進(jìn)場效果是怎樣的···
<!-- 頭像區(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)場
    }
  },
  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>

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

2. 離場

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

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

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

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


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

這里我說下對圖片綁定v-autofix, 實(shí)現(xià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ǔn), 按比例縮放,然后偏移最長邊和當(dā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這么注冊一個指令, 參照vue directive

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

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

2. 了解我們的需求

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

實(shí)際圖
效果圖

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

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

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

let img = new Image()
      img.onload = () => {
        // 以長度小的邊為基準(zhǔn), 按比例縮放,然后偏移最長邊和當(dā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的偏移長寬差一半來實(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ǔn), 按比例縮放,然后偏移最長邊和當(dā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啦
這是個相對簡單的指令應(yīng)用,目前也只用了el 的操作,還有更完善的實(shí)現(xiàn),就需要一起探討學(xué)習(xí)啦

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容