小程序與動畫的(de)故事 - 新聞資訊 - 雲南小程序開發|雲南軟件開發|雲南網站建設-昆明融晨信息技術有限公司

159-8711-8523

雲南網建設/小程序開發/軟件開發

知識

不(bù)管是(shì)網站,軟件還是(shì)小程序,都要(yào / yāo)直接或間接能爲(wéi / wèi)您産生價值,我們在(zài)追求其視覺表現的(de)同時(shí),更側重于(yú)功能的(de)便捷,營銷的(de)便利,運營的(de)高效,讓網站成爲(wéi / wèi)營銷工具,讓軟件能切實提升企業内部管理水平和(hé / huò)效率。優秀的(de)程序爲(wéi / wèi)後期升級提供便捷的(de)支持!

您當前位置>首頁 » 新聞資訊 » 小程序相關 >

小程序與動畫的(de)故事

發表時(shí)間:2021-4-13

發布人(rén):融晨科技

浏覽次數:66

一、故事序幕

時(shí)間一分一秒地(dì / de)流逝,小程序已伴随我們三載有餘,今天要(yào / yāo)講的(de)是(shì)關于(yú)小程序與動畫的(de)故事:從前...

二、故事開頭

一提小程序與動畫,首先想到(dào)的(de)是(shì)什麽?嗯,微信小程序獨創了(le/liǎo)一套動畫玩法,官方支持3種動畫方案,分别是(shì) createAnimation 、 this.animate 和(hé / huò) CSS3動畫 。

1. createAnimation 與 Animation

創建一個(gè)動畫實例animation。調用實例的(de)方法來(lái)描述動畫。最後通過動畫實例的(de)export方法導出(chū)動畫數據傳遞給組件的(de)animation屬性。

var animation = wx.createAnimation({
  transformOrigin: "50% 50%",
  duration: 1000,
  timingFunction: "ease",
  delay: 0
})

// step() 表示一組動畫的(de)完成,可以(yǐ)在(zài)一組動畫中調用任意多個(gè)動畫方法
// 一組動畫中的(de)所有動畫會同時(shí)開始,一組動畫完成後才會進行下一組動畫
animation.translate(150, 0).rotate(180).step()
animation.opacity(0).scale(0).step()
this.setData({
  animationData: animation.export()
})

2. 關鍵幀動畫 this.animate 接口

從小程序基礎庫 2.9.0 開始支持一種更友好的(de)動畫創建方式,用于(yú)代替舊的(de) wx.createAnimation 。它具有更好的(de)性能和(hé / huò)更可控的(de)接口。在(zài)頁面或自定義組件中,當需要(yào / yāo)進行關鍵幀動畫時(shí),可以(yǐ)使用 this.animate 接口。

this.animate(selector, keyframes, duration, callback)

官方給出(chū)的(de)例子(zǐ):

this.animate('#container', [
    { opacity: 1.0, rotate: 0, backgroundColor: '#FF0000' },
    { opacity: 0.5, rotate: 45, backgroundColor: '#00FF00'},
    { opacity: 0.0, rotate: 90, backgroundColor: '#FF0000' },
    ], 5000, function () {
      this.clearAnimation('#container', { opacity: true, rotate: true }, function () {
        console.log("清除了(le/liǎo)#container上(shàng)的(de)opacity和(hé / huò)rotate屬性")
      })
  }.bind(this))

3. css3動畫

這(zhè)是(shì)界面動畫的(de)常見方式,CSS 動畫運行效果良好,甚至在(zài)低性能的(de)系統上(shàng)。渲染引擎會使用跳幀或者其他(tā)技術以(yǐ)保證動畫表現盡可能的(de)流暢。

利用樣式實現小程序動畫,用法和(hé / huò)css用法相似,定義好指定的(de)動畫類名後給元素加上(shàng)即可。

這(zhè)是(shì)一個(gè)模仿心跳的(de)動畫:

@keyframes heartBeat {
  0% {
    transform: scale(1);
  }

  14% {
    transform: scale(1.3);
  }

  28% {
    transform: scale(1);
  }

  42% {
    transform: scale(1.3);
  }

  70% {
    transform: scale(1);
  }
}

.heartBeat {
  animation-name: heartBeat;
  animation-duration: 1.3s;
  animation-timing-function: ease-in-out;
}

三、故事發展

故事的(de)設定是(shì)這(zhè)樣子(zǐ)的(de):需要(yào / yāo)支持多種預設的(de)動畫效果配置,且實現進場動畫、強調動畫、退場動畫按順序運行。

如下,“3件5折/2件7折/1件9折”的(de)文本 設置了(le/liǎo) 進場動畫-從小到(dào)大(dà) 以(yǐ)及 強調動畫-脈沖 的(de)動畫效果:

生成的(de)小程序效果:

Taro 是(shì)小程序的(de)好夥伴,而(ér)且基于(yú)故事的(de)設定,H5 還是(shì)要(yào / yāo)點飯吃的(de)。

要(yào / yāo)想快速進入故事高潮,不(bù)得不(bù)采用一些取巧的(de)手段了(le/liǎo),決定采用市面上(shàng)常見的(de) Animate.css 動畫庫來(lái)支持多種預設的(de)動畫效果!

1. 支持多種動畫配置

Animate.css是(shì)一個(gè)可在(zài)您的(de)Web項目中使用的(de)即用型跨浏覽器動畫庫,預設了(le/liǎo)抖動(shake)、閃爍(flash)、彈跳(bounce)、翻轉(flip)、旋轉(rotateIn/rotateOut)、淡入淡出(chū)(fadeIn/fadeOut)等97種動畫效果。 官網 首頁即可查看所有動畫效果。

要(yào / yāo)支持多種動畫配置,考慮将 animate.css 這(zhè)個(gè)非常棒的(de)css庫引入到(dào)小程序内使用。

從 https://github.com/animate-cs... 下載源碼,将 .css 文件 改名爲(wéi / wèi) .wxss 或者.scss 文件,在(zài)頁面或組件中引入樣式文件即可。

import './animate.scss'

Animate.css 的(de)使用非常簡單,因爲(wéi / wèi)它是(shì)把不(bù)同的(de)動畫類型綁定到(dào)了(le/liǎo)不(bù)同的(de)類裏,所以(yǐ)想用哪種動畫,隻需要(yào / yāo)把相應的(de)類添加到(dào)元素上(shàng)就(jiù)可以(yǐ)盡情享用了(le/liǎo)。

由于(yú)小程序對代碼包的(de)大(dà)小限制,因此可删除 animate.css 中所有 @-webkit- 等前綴的(de)樣式減少一半體積,甚至直接使用 @keyframes 的(de)代碼,即去掉類名的(de)方式調用。

2. 執行完一個(gè)動畫後接着執行另一個(gè)動畫 ?

從上(shàng)文可知,采用的(de)是(shì)CSS3的(de)動畫方案,基本決定了(le/liǎo)故事的(de)下一個(gè)發展階段。

如果要(yào / yāo)實現進場動畫、強調動畫、退場動畫按順序運行,那麽需要(yào / yāo)監聽上(shàng)一個(gè)動畫結束,緊接着運行下一個(gè)動畫。

動畫過程中,微信小程序可以(yǐ)使用 bindtransitionend 、 bindanimationstart 、 bindanimationiteration 、 bindanimationend 來(lái)監聽動畫事件。

在(zài) Taro 中内置組件的(de)事件依然是(shì)以(yǐ) on 開頭的(de),即 onTransitionEnd 、 onAnimationStart 、 onAnimationIteration 、 onAnimationEnd 。

注意:監聽動畫事件都不(bù)是(shì)冒泡事件,需要(yào / yāo)綁定在(zài)真正發生了(le/liǎo)動畫的(de)節點上(shàng)才會生效。

要(yào / yāo)實現進場之(zhī)前不(bù)可見,退場後不(bù)可見,設置 animation-fill-mode: both 即可,且不(bù)可移除樣式,因爲(wéi / wèi)退場動畫的(de)效果效果 會失效,元素又顯示出(chū)來(lái)了(le/liǎo)。

可能還得處理其他(tā)行爲(wéi / wèi),比如 消失的(de)元素 實際可能還占位,交互點擊的(de)行爲(wéi / wèi)最好解綁。

<View
  onAnimationEnd={this.onAnimationEnd}
>
  {this.props.children}
</View>

四、故事高潮

故事都鋪墊好了(le/liǎo),終于(yú)來(lái)到(dào)了(le/liǎo)高潮。

眼尖的(de)人(rén)兒也(yě)發現了(le/liǎo),上(shàng)文GIF圖 “生成的(de)小程序效果” 還實現了(le/liǎo)滾動到(dào)可視區域才開始執行動畫的(de)效果。

這(zhè)是(shì)老生常談的(de)話題了(le/liǎo),那怎麽在(zài)小程序側實現呢?

方案一:頁面滾動模式

  1. 小程序利用 onPageScroll 的(de) API 監聽用戶滑動頁面事件,可獲取 scrollTop :頁面在(zài)垂直方向已滾動的(de)距離(單位px)。
  2. Taro.createSelectorQuery 獲取元素在(zài)顯示區域的(de)豎直滾動位置。
  3. 基上(shàng)計算是(shì)否在(zài)可視區域來(lái)判斷是(shì)否要(yào / yāo)開始動畫。

方案二:觀察者模式

  1. 不(bù)支持 onPageScroll 的(de)情況下,則需要(yào / yāo)使用 Taro.createIntersectionObserver 獲取目标節點與參照區域的(de)相交比例觸發相關的(de)回調函數,即觀察者模式。

代碼奉上(shàng)

(1) Taro獲取當前頁面的(de)方式

首先我們要(yào / yāo)知道(dào)如何獲取當前頁面棧,數組中第一個(gè)元素爲(wéi / wèi)首頁,最後一個(gè)元素爲(wéi / wèi)當前頁面:

getCurrentPage () {
  const pages = Taro.getCurrentPages ? Taro.getCurrentPages() : [{}]
  const currentPage = pages[pages.length - 1]
  return currentPage
}

(2) 初始化頁面滾動

判斷使用頁面滾動模式還是(shì)觀察者模式:

initPageScroll () {
  const env = Taro.getEnv()
  const currentPage = this.getCurrentPage()

  // 獲取onPageScroll方法
  const onPageScroll = currentPage.onPageScroll

  // 頁面滾動模式:h5 或「小程序頁面有onPageScroll鈎子(zǐ)」使用統一的(de)代碼
  const isPageScroll =
    env === Taro.ENV_TYPE.WEB ||
    (env !== Taro.ENV_TYPE.WEB && onPageScroll !== undefined)

  // 觀察者模式:小程序頁面沒有 onPageScroll 鈎子(zǐ),使用 Taro.createIntersectionObserver 監聽
  const isObserver = env !== Taro.ENV_TYPE.WEB && Taro.createIntersectionObserver

  if (isPageScroll) {
    this.listenPageScroll(currentPage)
  } else if (isObserver) {
    this.observePageScroll()
  }
}

(3) 頁面滾動模式

首先在(zài)類外頭定義一個(gè)多環境的(de) pageScroll 鈎子(zǐ),支持小程序和(hé / huò)H5:

const createPageScroll = function(page) {
  const env = Taro.getEnv()
  let onPageScroll = () => {}

  if (env !== Taro.ENV_TYPE.WEB) {
    // 小程序
    const prevOnPageScroll = page.onPageScroll.bind(page)
    page.onPageScroll = e => {
      prevOnPageScroll(e)
      onPageScroll(e)
    }
  } else if (env === Taro.ENV_TYPE.WEB) {
    // H5
    window.addEventListener("scroll", () => {
      onPageScroll({ scrollTop: window.scrollY })
    })
  }

  return nextOnPageScroll => {
    onPageScroll = nextOnPageScroll
  }
}

使用上(shàng)述定義的(de)createPageScroll方法,開始監聽滾動:

listenPageScroll (currentPage) {
  const pageScroll = createPageScroll(currentPage)
  pageScroll(this.onScroll)
}

獲取距離頁面頂部高度來(lái)判斷是(shì)否要(yào / yāo)開始動畫:

知識點:

  • 在(zài) Taro 的(de)頁面和(hé / huò)組件類中, this 指向的(de)是(shì) Taro 頁面或組件的(de)實例,而(ér)通過 this.$scope 獲取 Taro 的(de)頁面和(hé / huò)組件所對應的(de)小程序原生頁面和(hé / huò)組件的(de)實例。
  • Taro.createSelectorQuery 返回一個(gè) SelectorQuery 對象實例。在(zài)自定義組件或包含自定義組件的(de)頁面中,應使用 this.createSelectorQuery() 來(lái)代替。
  • SelectorQuery對象實例可進一步查詢節點信息,提供 select 、 in 、 exec 等方法。
  • NodesRef 的(de) boundingClientRect 用于(yú)查詢節點的(de)布局位置,相對于(yú)顯示區域,以(yǐ)像素爲(wéi / wèi)單位,其功能類似于(yú) DOM 的(de) getBoundingClientRect。
onScroll = () => {
  const query = Taro.createSelectorQuery().in(this.$scope)
  query
    .select(`.animation-${this.uniq}`)
    .boundingClientRect(res => {
      if (!res) return

      let resTop = res.top
      const distance = res.height / 2
      const isStartAnimation = resTop + distance < this.windowHeight
      if (isStartAnimation && !this.isAnimated) {
        this.startAnimation()
        // 動畫隻出(chū)現一次
        this.isAnimated = true
      }
    })
    .exec()
}

(4) 觀察者模式:

知識點:

  • Taro.createIntersectionObserver 創建并返回一個(gè) IntersectionObserver 對象實例。在(zài)自定義組件或包含自定義組件的(de)頁面中,應使用 this.createIntersectionObserver([options]) 來(lái)代替。
  • IntersectionObserver 對象,用于(yú)推斷某些節點是(shì)否可以(yǐ)被用戶看見、有多大(dà)比例可以(yǐ)被用戶看見。
  • IntersectionObserver 的(de) relativeToViewport 方法 指定頁面顯示區域作爲(wéi / wèi)參照區域之(zhī)一。
  • IntersectionObserver 的(de) observe 指定目标節點并開始監聽相交狀态變化情況,其中 res.intersectionRatio 指相交區域占目标節點的(de)布局區域的(de)比例。
observePageScroll () {
  const navObserver = Taro.createIntersectionObserver(this.$scope, {
    initialRatio: 0.5,
    thresholds: [0.5]
  })
  navObserver.relativeToViewport()
  navObserver.observe(`.animation-${this.uniq}`, res => {
  const isStartAnimation = !this.isAnimated && res.intersectionRatio > 0.5
    if (isStartAnimation) {
      this.startAnimation()
      // 動畫隻出(chū)現一次
      this.isAnimated = true
    }
  })
}

五、故事結尾

小程序與動畫的(de)故事遠遠沒有結束,縱使故事有了(le/liǎo)開頭,你看到(dào)的(de)隻是(shì)故事的(de)萬種可能的(de)其中一種。

故事就(jiù)要(yào / yāo)告一段落了(le/liǎo),小程序的(de)故事還在(zài)持續奔跑,感謝 微信小程序 和(hé / huò) taro 的(de)文檔。

歡迎關注凹凸實驗室博客: aotu.io

或者關注凹凸實驗室公衆号(AOTULabs),不(bù)定時(shí)推送文章。

相關案例查看更多