時間一分一秒地流逝,小程序已伴隨咱們三載有餘,今天要講的是關於小程序與動畫的故事:從前...css
一提小程序與動畫,首先想到的是什麼?嗯,微信小程序首創了一套動畫玩法,官方支持3種動畫方案,分別是 createAnimation
、 this.animate
和 CSS3動畫
。css3
createAnimation
與Animation
建立一個動畫實例animation。調用實例的方法來描述動畫。最後經過動畫實例的export方法導出動畫數據傳遞給組件的animation屬性。
var animation = wx.createAnimation({ transformOrigin: "50% 50%", duration: 1000, timingFunction: "ease", delay: 0 }) // step() 表示一組動畫的完成,能夠在一組動畫中調用任意多個動畫方法 // 一組動畫中的全部動畫會同時開始,一組動畫完成後纔會進行下一組動畫 animation.translate(150, 0).rotate(180).step() animation.opacity(0).scale(0).step() this.setData({ animationData: animation.export() })
this.animate
接口從小程序基礎庫 2.9.0 開始支持一種更友好的動畫建立方式,用於代替舊的 wx.createAnimation 。它具備更好的性能和更可控的接口。在頁面或自定義組件中,當須要進行關鍵幀動畫時,可使用 this.animate 接口。
this.animate(selector, keyframes, duration, callback)
官方給出的例子:git
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("清除了#container上的opacity和rotate屬性") }) }.bind(this))
這是界面動畫的常見方式,CSS 動畫運行效果良好,甚至在低性能的系統上。渲染引擎會使用跳幀或者其餘技術以保證動畫表現儘量的流暢。
利用樣式實現小程序動畫,用法和css用法類似,定義好指定的動畫類名後給元素加上便可。github
這是一個模仿心跳的動畫:web
@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; }
故事的設定是這樣子的:須要支持多種預設的動畫效果配置,且實現進場動畫、強調動畫、退場動畫按順序運行。小程序
以下,「3件5折/2件7折/1件9折」的文本 設置了 進場動畫-從小到大 以及 強調動畫-脈衝 的動畫效果:微信小程序
生成的小程序效果:數組
Taro 是小程序的好夥伴,並且基於故事的設定,H5 仍是要點飯吃的。瀏覽器
要想快速進入故事高潮,不得不採用一些取巧的手段了,決定採用市面上常見的 Animate.css 動畫庫來支持多種預設的動畫效果!微信
Animate.css是一個可在您的Web項目中使用的即用型跨瀏覽器動畫庫,預設了抖動(shake)、閃爍(flash)、彈跳(bounce)、翻轉(flip)、旋轉(rotateIn/rotateOut)、淡入淡出(fadeIn/fadeOut)等97種動畫效果。 官網首頁便可查看全部動畫效果。
要支持多種動畫配置,考慮將 animate.css 這個很是棒的css庫引入到小程序內使用。
從https://github.com/animate-cs... 下載源碼,將 .css 文件 更名爲 .wxss 或者.scss 文件,在頁面或組件中引入樣式文件便可。
import './animate.scss'
Animate.css 的使用很是簡單,由於它是把不一樣的動畫類型綁定到了不一樣的類裏,因此想用哪一種動畫,只須要把相應的類添加到元素上就能夠盡情享用了。
因爲小程序對代碼包的大小限制,所以可刪除 animate.css 中全部 @-webkit-
等前綴的樣式減小一半體積,甚至直接使用 @keyframes
的代碼,即去掉類名的方式調用。
從上文可知,採用的是CSS3的動畫方案,基本決定了故事的下一個發展階段。
若是要實現進場動畫、強調動畫、退場動畫按順序運行,那麼須要監聽上一個動畫結束,緊接着運行下一個動畫。
動畫過程當中,微信小程序可使用 bindtransitionend
、bindanimationstart
、bindanimationiteration
、bindanimationend
來監聽動畫事件。
在 Taro 中內置組件的事件依然是以 on 開頭的,即 onTransitionEnd
、onAnimationStart
、onAnimationIteration
、onAnimationEnd
。
注意:監聽動畫事件都不是冒泡事件,須要綁定在真正發生了動畫的節點上纔會生效。
要實現進場以前不可見,退場後不可見,設置 animation-fill-mode: both
便可,且不可移除樣式,由於退場動畫的效果效果 會失效,元素又顯示出來了。
可能還得處理其餘行爲,好比 消失的元素 實際可能還佔位,交互點擊的行爲最好解綁。
<View onAnimationEnd={this.onAnimationEnd} > {this.props.children} </View>
故事都鋪墊好了,終於來到了高潮。
眼尖的人兒也發現了,上文GIF圖 「生成的小程序效果」 還實現了滾動到可視區域纔開始執行動畫的效果。
這是老生常談的話題了,那怎麼在小程序側實現呢?
onPageScroll
的 API 監聽用戶滑動頁面事件,可獲取 scrollTop
:頁面在垂直方向已滾動的距離(單位px)。Taro.createSelectorQuery
獲取元素在顯示區域的豎直滾動位置。onPageScroll
的狀況下,則須要使用 Taro.createIntersectionObserver
獲取目標節點與參照區域的相交比例觸發相關的回調函數,即觀察者模式。(1) Taro獲取當前頁面的方式
首先咱們要知道如何獲取當前頁面棧,數組中第一個元素爲首頁,最後一個元素爲當前頁面:
getCurrentPage () { const pages = Taro.getCurrentPages ? Taro.getCurrentPages() : [{}] const currentPage = pages[pages.length - 1] return currentPage }
(2) 初始化頁面滾動
判斷使用頁面滾動模式仍是觀察者模式:
initPageScroll () { const env = Taro.getEnv() const currentPage = this.getCurrentPage() // 獲取onPageScroll方法 const onPageScroll = currentPage.onPageScroll // 頁面滾動模式:h5 或「小程序頁面有onPageScroll鉤子」使用統一的代碼 const isPageScroll = env === Taro.ENV_TYPE.WEB || (env !== Taro.ENV_TYPE.WEB && onPageScroll !== undefined) // 觀察者模式:小程序頁面沒有 onPageScroll 鉤子,使用 Taro.createIntersectionObserver 監聽 const isObserver = env !== Taro.ENV_TYPE.WEB && Taro.createIntersectionObserver if (isPageScroll) { this.listenPageScroll(currentPage) } else if (isObserver) { this.observePageScroll() } }
(3) 頁面滾動模式
首先在類外頭定義一個多環境的 pageScroll 鉤子,支持小程序和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 } }
使用上述定義的createPageScroll方法,開始監聽滾動:
listenPageScroll (currentPage) { const pageScroll = createPageScroll(currentPage) pageScroll(this.onScroll) }
獲取距離頁面頂部高度來判斷是否要開始動畫:
知識點:
this
指向的是 Taro 頁面或組件的實例,而經過 this.$scope
獲取 Taro 的頁面和組件所對應的小程序原生頁面和組件的實例。Taro.createSelectorQuery
返回一個 SelectorQuery 對象實例。在自定義組件或包含自定義組件的頁面中,應使用 this.createSelectorQuery() 來代替。select
、 in
、exec
等方法。boundingClientRect
用於查詢節點的佈局位置,相對於顯示區域,以像素爲單位,其功能相似於 DOM 的 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() // 動畫只出現一次 this.isAnimated = true } }) .exec() }
(4) 觀察者模式:
知識點:
Taro.createIntersectionObserver
建立並返回一個 IntersectionObserver 對象實例。在自定義組件或包含自定義組件的頁面中,應使用 this.createIntersectionObserver([options]) 來代替。relativeToViewport
方法 指定頁面顯示區域做爲參照區域之一。observe
指定目標節點並開始監聽相交狀態變化狀況,其中 res.intersectionRatio
指相交區域佔目標節點的佈局區域的比例。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() // 動畫只出現一次 this.isAnimated = true } }) }
小程序與動畫的故事遠遠沒有結束,縱使故事有了開頭,你看到的只是故事的萬種可能的其中一種。
故事就要告一段落了,小程序的故事還在持續奔跑,感謝 微信小程序 和 taro 的文檔。
歡迎關注凹凸實驗室博客:aotu.io
或者關注凹凸實驗室公衆號(AOTULabs),不定時推送文章。