日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Web 仿 App 动画竟然引出了“性能杀手”

發(fā)布時間:2024/4/13 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Web 仿 App 动画竟然引出了“性能杀手” 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

本文作者:楊曄

原創(chuàng)聲明:本文為閱文前端團隊 YFE 成員出品,請尊重原創(chuàng),轉(zhuǎn)載請聯(lián)系公眾號 ( id: yuewen_YFE ) 獲取授權,并注明作者、出處和鏈接。

背景

在我參與開發(fā)的對話小說項目過程中,我們發(fā)現(xiàn)創(chuàng)意類的活動對拉升轉(zhuǎn)化數(shù)據(jù)很有幫助。經(jīng)過調(diào)研,這款對話式小說產(chǎn)品的用戶群體大多數(shù)都是比較年輕的 90-95 后,所以最后結論是希望以目前業(yè)界年輕化 APP 流行的交互形式 —— 《滑卡片》對推書活動做一次改版,也同時希望這個頁面能和產(chǎn)品本身結合作為一個常駐功能頁,我們先來看一下最終的實現(xiàn)效果:

是不是挺流暢?接下來我會按照當時開發(fā)的思路和過程來講述開發(fā)中經(jīng)歷了什么。

參考

在極為用心的設計師交付設計稿后,她還特地使用 flinto ??做了交互原型來輔助我達到策劃預期的效果。

《flinto 交互稿》

見到這份貼心的交互稿后,我首先想到的就是先去參考即刻 App 中的探索頁,以及交友軟件《探探》的交互形式,他們的交互效果分別如下:

《探探 App 》

《即刻 App - 探索》

兩者效果非常相似吧??但和我這次需求不同的是:我們的頁面是內(nèi)嵌在起點讀書 App 內(nèi)的 H5,而以上兩者皆是由原生 App 開發(fā)實現(xiàn)的效果,所以我對“能否高度還原”以及”如何保證良好的性能”還是產(chǎn)生了一點擔憂 ?。

嘗試

樣式重構思路 在獲取真實數(shù)據(jù)和開發(fā)復雜邏輯之前,我先用草圖整理了一下實現(xiàn)思路:

如圖所示:

  • 初始狀態(tài)為3張卡片疊在一起,要有 3D 立體感,在拖拽的時候能露出后面兩張
  • 拖拽第一張時卡片需要跟隨手指滑動方向,超過一定距離放開手指后卡片飛出,后面的卡片自動往前推進一張,頁面中始終需要 3 張卡片可見狀態(tài)。

根據(jù)以上思路,既然要有 3D 立體感和推進動效,如果單獨使用 z-index 來實現(xiàn)肯定不能滿足,所以我選擇使用 translateZ 來搭配完成這個堆疊卡片的推進效果,因為他能更好的顯示出三維空間景深。如此一來,卡片往前推進和被扔出的卡片自動飛出等動效都可以完全交給 CSS3 動畫過渡來完成。

樣式代碼(主要結構屬性)

.card_container {position: relative;width: 6.86rem;height: 8.96rem;perspective: 1000px;perspective-origin: 50% 150%;-webkit-perspective: 1000px;-webkit-perspective-origin: 50% 150%; } .card {transform-style: preserve-3d;width: 100%;height: 100%;position: absolute;opacity: 0; } 復制代碼

堆疊的卡片需要有一個父容器,讓所有堆疊的卡片產(chǎn)生 3D 透視效果。

HTML 和綁定方法

<div class="card_container"><divv-for="(item,index) in dataArr":key="item.id"ondragstart="return false"class="card":style="[cardTransform(index),indexTransform(index)]"@touchstart.stop.capture="touchStart($event,index)"@touchmove.stop.capture="touchMove($event)"@touchend.stop.capture="touchEnd($event,index)"@mousedown.stop.capture="touchStart($event)"@mousemove.stop.capture="touchMove($event)"@mouseup.stop.capture="touchEnd"@transitionend="onTransitionEnd(index)"> </div> 復制代碼

我們還需要一些關鍵變量來記錄一些可能實時變化的屬性:

// 當前展示的圖片index currentIndex: 0, // 記錄偏移量 displacement: {x: 0,y: 0 }, // 位置信息 position: {start: { x: 0, y: 0 },end: { x: 0, y: 0 },direction: 1, // 滑動方向,左是-1,右是1swipping: false // 是否在拖動交換過程中 }, // 記錄每一個丟出去的方向 directionArr: [], // 顯示圖片的堆疊數(shù)量 visible: 3, // 視口寬度 winWidth: 0, // 滑動閾值 slideWidth: 70, // 超過閾值時的自動偏移量 offsetWidth: 120, 復制代碼

再給 style 綁上 2 個初始化的方法。 cardTransform 用來初始化每張卡片的樣式,indexTransform 用來初始化第一張卡片的樣式。

// 初始化每張卡片的樣式 cardTransform (index) {let style = {}//卡片自動位移距離(飛出屏幕多遠)let offset = 0if (this.directionArr[index] === 1) {offset = 800} else if (this.directionArr[index] === -1) {offset = -800}style['z-index'] = this.currentIndex - index + this.visible style['transform'] = `translate3d(0,0,${(this.currentIndex - index) * 60}px)`//讓藏在后面的卡片縮小樣式堆疊在一起并透明不顯示。一旦飛走一張,下一張卡片會自動過渡動畫往前推進if (index - this.currentIndex < 0) {style['opacity'] = 0style['transform'] = `translate3d(${this.position.end.x + offset}px,${this.position.end.y}px,${(this.currentIndex - index) * 60}px) rotate(${this.position.direction * -65}deg)`}// 非手勢滑動狀態(tài)才添加過渡動畫if (!this.position.swipping) {style['transitionTimingFunction'] = 'ease'style['transitionDuration'] = 300 + 'ms'}return style }, // 第一張卡片的樣式 indexTransform (index) {let style = {}if (index === this.currentIndex) {style['transform'] = `translate3d(${this.displacement.x}px,${this.displacement.y}px,${(this.currentIndex - index) * 60}px) rotate(${this.displacement.x / this.winWidth * -65}deg)`}// 非手勢滑動狀態(tài)才添加過渡動畫if (!this.position.swipping) {style['transitionTimingFunction'] = 'ease'style['transitionDuration'] = 300 + 'ms'}return style} 復制代碼

之后的拖拽卡片 touch 事件就相當于以前寫拖拽 DIV 那樣簡單容易,返回上一張和背景過渡等細節(jié)的方法這里就不再做過多的代碼展示了。

到此為止,使用了四本數(shù)的 mock 數(shù)據(jù),一切都很順利,動畫也非常流暢:

App Webview crash ?

接著我開始請求真實數(shù)據(jù),并做了一系列的優(yōu)化,比如:

  • 全機型適配卡片屏幕居中。
  • 記錄用戶操作,拖拽扔出時的方向存入 localStorage (用戶再次打開時看到的第一張卡片依然是之前離開時的,體驗更像是在App內(nèi))
  • 優(yōu)化減少請求,首次進頁面時加載 2 張圖片,之后每飛走一張卡片時加載下一張圖片。
  • 優(yōu)化之后,在 PC Chrome 移動端模式下一切看起來都是那么順利,我自以為不會有什么問題,最后發(fā)布到測試環(huán)境用 App 掃碼打開后看到的卻是這一幕:

    我一開始對性能的擔憂終于還是發(fā)生了,App 內(nèi)直接發(fā)生了崩潰,我再嘗試用移動端瀏覽器打開,并沒有發(fā)生崩潰,但是操作起來很不流暢,再回到 PC 上體驗了一次,依然感知不到有什么卡頓,我想可能是由于手機硬件不如 PC, 發(fā)生崩潰的原因可能是 3D 渲染或者性能方面出現(xiàn)了問題。根據(jù)這個思路,我打算從數(shù)據(jù)上進行一次對比查看導致崩潰的關鍵要素是什么。

    性能對比

    首先使用 Chrome 自帶的 Performance 進行了長達 7 秒的頁面錄制,在 7 秒鐘我瘋狂的對卡片操作了一番,最后得出的性能圖如下:

    除了有一個小警告:Handler took 之外并證明不了什么嚴重的問題。 我打算再監(jiān)控一下渲染性能,我從 Chrome 的更多工具里調(diào)起了 Rendering 面板

    在所有的選項全部打上勾后,造成問題的原因一下子就暴露了!

    OMG ?,幀率只有 18 fps,而且原來所有的卡片都重合在了一起并進行了渲染。我馬上意識到開發(fā)中的錯誤點:那些隱藏的卡片雖然 把透明度設置為了 0,但看不見并不代表不會被渲染,那些被隱藏的卡片在每一次卡片飛出動畫后都在實時被渲染推進動畫,嚴重損耗了性能。

    也就是說,opacity 造成了頁面的大量 reflow,這時我才想起,opacity 和 visibility 都會造成回流,而只要有 reflow 必定會造成 repaint,只有 display:none 可以避雷,因為它徹底脫離了文檔流,在開發(fā)這個需求以來,我一直在優(yōu)化頁面還原度和動效,卻忘記了這重要的一點。

    優(yōu)化

    知道了問題的關鍵就好辦多了,opacity 依然要保留,因為推進動效的過渡需要透明度來美化,光用 display 會變得非常生硬。既然用的是 VUE,那就更好辦了,首先給數(shù)據(jù)中的數(shù)組全部添加上 display 屬性,默認為 false,然后給 card 元素綁上了 :class="{display:item.display}",再將 css 的 card 樣式全部設置為 display:none

    <div class="card_container"><divv-for="(item,index) in dataArr":key="item.id"ondragstart="return false"class="card":style="[cardTransform(index),indexTransform(index)]"@touchstart.stop.capture="touchStart($event,index)"@touchmove.stop.capture="touchMove($event)"@touchend.stop.capture="touchEnd($event,index)"@mousedown.stop.capture="touchStart($event)"@mousemove.stop.capture="touchMove($event)"@mouseup.stop.capture="touchEnd"@transitionend="onTransitionEnd(index)":class="{display:item.display}"> </div> 復制代碼

    在需要顯示的時候讓它變?yōu)?true,隨即樣式變?yōu)?block 。

    .card.display {display: block;opacity: 1; } 復制代碼

    舉個例子,比如我在 touchEnd 時有一個卡片移動的方法 moveNext。

    touchEnd () {this.position.swipping = falsethis.position.end['x'] = this.displacement.xthis.position.end['y'] = this.displacement.y// 判斷滑動距離超過設定值時,自動飛出if (this.displacement.x > this.slideWidth) {this.moveNext(1) //往右} else (this.displacement.x < -this.slideWidth) {this.moveNext(-1) //往左} this.$nextTick(() => {this.displacement.x = 0this.displacement.y = 0this.isDrag = false}) } 復制代碼

    我們就可以在 moveNext 時對 index 進行操作。moveNext 中需要對當前顯示的第一張卡片和后面堆疊的都添加顯示,已經(jīng)消失的卡片變?yōu)殡[藏,如此循環(huán)無縫銜接。另外,由于數(shù)據(jù)是不確定的,為避免某些極端情況(例如首張卡片再往前或者最后倒數(shù)幾張后都會出現(xiàn)沒有更多卡片的情況,所以還需要做細節(jié)容錯處理)。

    moveNext (direction) {this.position.direction = direction// 防止在最后倒數(shù)幾張時操時出錯try {this.dataArr[this.currentIndex + 3].display = true} catch (e) {}// 防止在第一張時操作出錯if (this.currentIndex > 0) {try {this.dataArr[this.currentIndex - 1].display = false} catch (e) {}}this.currentIndex++ //每次讓下一張卡片往前推進,反之 -- 就是返回上一張!direction ? this.position.end['x'] -= this.offsetWidth : this.position.end['x'] += this.offsetWidththis.position.end['y'] += this.offsetWidth / 2} 復制代碼

    在一番調(diào)整優(yōu)化后,我重新調(diào)起了 Rendering 面板查看結果:

    和預想的一樣,幀數(shù)達到正常的 60 fps,不管如何操作,始終只有 3 張卡片是可見(被渲染的),性能得到了大大提升,重新回到 App 中訪問也沒有再遇到崩潰的問題。

    掃碼體驗(使用起點 APP 查看效果更佳)

    總結

    經(jīng)過這次 App webview 引起的崩潰事件,我從中吸取到了一些經(jīng)驗和總結,也希望對閱讀此文章的你有所幫助 ?。

  • 用 Web 模擬 App 原生動畫時,特別是在移動端,使用高階屬性去實時動態(tài)地改變元素時需要特別謹慎。
  • “肉眼感知”并不準確,也不能作為衡量依據(jù),一切要以開發(fā)工具中的性能數(shù)據(jù)為基準來證明。
  • reflow 和 repaint 在 PC 端只要不是懷有明知山有虎,偏向虎山行的心態(tài)去寫代碼,幾乎不會引發(fā)性能問題,但是移動端的渲染能力和 PC 端差了一大截,一個不小心,由 CSS 引發(fā) reflow 和 repaint 就會成為移動端的“性能殺手”。所以,在完成需求和動效前,對自己的方案提前進行一次性能的心理預期也是很有必要的,在考量頁面性能的時候分析 reflow 和 repaint 也算是一個切入點。
  • 查看更多分享,請關注閱文集團前端團隊公眾號:

    轉(zhuǎn)載于:https://juejin.im/post/5d0b61c3f265da1bc37f153a

    總結

    以上是生活随笔為你收集整理的Web 仿 App 动画竟然引出了“性能杀手”的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。