[前端]實戰年終盤點

NO IMAGE

導語:年終了,聽說你也在開發年終盤點?那,也許你可以看看這篇文章

聖誕節的時候ABCmouse為用戶精心準備了一份聖誕禮物,你也想看下嗎?快來掃下這個神奇的二維碼吧…

[前端]實戰年終盤點

好吧,知道你可能不想掃碼’__’,直接看下圖吧(截取了其中一段)

[前端]實戰年終盤點

當然這篇文章不是介紹整個開發過程(實際上本身開發週期很短,開發才三天,另外兩天bugfix和視覺還原,時間非常趕)。這篇文章主要記錄我在開發的過程的過程的一些經驗總結和遇到的坑。

坑一:視頻坑

這次的年終盤點在前面半部分是一個視頻,點擊播放視頻完成(或者跳過)之後正式進入主頁。

劃重點:在視覺設計初期我跟視覺反抗過,建議儘量不要在活動頁做內聯視頻播放,有的瀏覽器會挾持video標籤的播放,使用自己的方式實現,特別Android,會有很多兼容性問題,會比較影響用戶體驗。事實證明也確實如此,且聽我一一道來

播放視頻時內聯播放,這裡視頻播放只限制在微信和QQ內才能內聯播放。其他手機自帶瀏覽器直接會跳過這個視頻播放,後面我簡單說明下原因。

[前端]實戰年終盤點

為了實現視頻的內聯播放,我們可以藉助video標籤的playsinline屬性:

<video 
webkit-playsinline="true"
playsinline="true"
></video> 

另外為了能在視頻播放的時候在視頻上方顯示跳過按鈕,這裡我們需要用到X5內核視頻播放的一個屬性x5-video-player-type設置為h5-page之後,這樣就可以控制視頻在網頁內部同層播放,同時也可以在視頻上方顯示html元素。(具體可以看這裡: H5同層播放器接入規範)

<video x5-video-player-type="h5-page"></video>

因為瀏覽器的自身自動播放策略,視頻的自動播放需要用戶在當前頁面上有用戶行為產生,或者設置視頻靜音屬性muted,才能自動播放。而我們的視頻在前7.23s的時候會有視頻音樂的,因此播放時不能設置為靜音,所以無法做成自動播放,於是做成了如上圖所示,用戶點擊時才能開始播放。(另外在Android上是無法自動獲取視頻的第一幀的,所以這裡讓視覺取第一幀圖片給你吧)。

點擊視頻,終於要開始視覺設計的超級卡哇伊的視頻了。但是這些都是什麼鬼。。。

1、在Android設備下出現小窗播放

[前端]實戰年終盤點

時間很緊張,這裡沒處理。(o(╥﹏╥)o)

2、在Android設備下小窗播放完成後出現廣告頁?

[前端]實戰年終盤點

這個可不行。

解決方案:在視頻播放完成後馬上調用播放並暫停。

if (lib.browser.os.android) {
video.play();
setTimeout(function(){
video.pause();
});
}

其實在X5內核中還可以考慮使用mtt-playsinline屬性來強制使用系統播放器,從而拒絕視頻被攔截植入推薦視頻。時間不夠當時沒考慮到上面去。

<video
mtt-playsinline="true"
></video>

3、百花齊放、百舸爭流的Android手機自動瀏覽器的視頻播放。

這裡圖片太多,就不一一放了。親測出現過:

  • 自動橫屏播放
  • 懸浮置頂播放
  • 自動全屏播放
  • ….
[前端]實戰年終盤點

我解決不了了。。。跟視覺討論,客戶群體主要還是在微信和QQ,所以在手機自帶瀏覽器裡摸下小老鼠的屁股後直接跳過視頻播放,直奔主題。

坑二:音頻坑

視頻問題不完美解決後,你以為完了?我之前說過:視頻播放到7.23s的時候需要自動播放背景音樂,此時的小老鼠往上拋,出現叮叮噹 叮叮噹...的背景音樂,是不是很有節奏感?但是…

1、Android切換背景音樂的時候視頻暫停播放

沒錯就是卡在這裡…

[前端]實戰年終盤點

需要注意:在Android設備上視頻播放後同時使用audio標籤播放音頻時會導致視頻卡住。

幸虧組裡缺什麼也不會缺大佬,大佬說:這個問題我遇到過,你可以使用WebAudio播放音頻就OK了。關於WebAudio你可以點這裡,崇拜ing…

解決方案:在Android設備中使用WebAudio播放音頻,而在其它設備中使用audio標籤進行播放。(疑問解答:為什麼不統一用WebAudio?,因為在另外一個需要中出現過播放視頻時播放音頻在IOS設備中出現過破音,沒錯就是破音

if (lib.browser.os.android) {
this.player = new WebAudioPlayer(this.src);
} else {
this.player = new AudioPlayer(this.src);
}

(ps: 其中WebAudioPlayer和AudioPlayer是自己封裝的一個簡單庫)

2、IOS下音頻自動播放失效?

音頻的自動播放策略和視頻的一樣,設置靜音或者有用戶行為。但是點擊播放視頻的時候不是已經有了用戶行為,為什麼還是播放不了?IOS出於安全機制,不允許audio和video自動播放,所以當切換播放音頻播放時還是無法自動播放。

解決方案:在點擊觸發視頻播放的時候同時觸發音頻播放,只是馬上暫停。(在這裡你可以做下音頻預加載)

this.bgmusic.play();
setTimeout(function() {
self.bgmusic.pause();
});

寫到這裡,其實我很困了…

[前端]實戰年終盤點

3、切換後臺後背景音樂未停止播放

這個其實應該都遇到過,這裡簡單記錄下解決方案:監聽下visibilitychange事件,網頁被掛起時暫停背景音樂即可。呼起時繼續播放。

document.addEventListener("visibilitychange", function(event) {
if (document.hidden) { // 網頁被掛起
beforePlayPaused = !self.bgmusic.isPlaying();
self.bgmusic.pause();
} else { // 網頁被呼起
if (beforePlayPaused) {
return;
}
self.bgmusic.play();
}
});

除了坑,那還有一部分就是細節了。

細節一:下雪的效果

畢竟聖誕,畢竟小孩,有一個下雪的效果是不是小孩子更喜歡?

[前端]實戰年終盤點

這裡的效果很簡單,我們可以使用canvas繪製就可以,因為是全屏雪,所以這裡可以把canvas的層級可以放後一點,防止覆蓋上面層級的操作。

實現思路

可以將雪花和下雪的效果拆分為兩個實體類Snowflake和Snow。其中雪花可以給它一些透明度、大小、水平和垂直方向速度等屬性,當然還有它的水平和垂直座標。然後每幀更新下雪花的位置即可。甚至你可以給它來點風,可以讓它看起來更加真實。

下雪的時候以屏幕寬度為維度,設置雪花的數量用來控制雪的密度。如:

_initSnowflakes: function() {
var level = this.options.level,
baseNum = LevelEnum.hasOwnProperty(level) ? LevelEnum[level] : LevelEnum.middle,
snowflakesNum = parseInt(this.windowWidth / baseNum);
for (var i = 0; i < snowflakesNum; i++) {
this.snowflakes.push(new Snowflake(this.options));
}
}

然後繪製到canvas上,最後使用幀動畫來改變雪花的位置來實現下雪的效果。
我已經將這部分代碼進行了抽離併發布到github上,有興趣的可以瞭解下: canvas下雪效果

性能優化點

  • 使用requestAnimationFrame實現幀動畫
  • 雪花數量的控制
  • 監聽visibilitychange事件,在切換後臺的時候暫停canvas動畫,因為在Android設備上切換後臺後定時器還是在運行的。

細節二:騷氣的進度條

你可能沒注意到,在頁面的左上角掛了一串彩燈,每進入一個場景,燈就會亮一盞。

[前端]實戰年終盤點

好吧,這裡我還是用canvas繪製的,但是時間不夠,我本來可以做的足夠好,而不是目前這麼粗糙的效果…

需要注意的是彩燈亮了之後是一個漸變,這裡使用了createRadialGradient徑向漸變來繪製燈光效果。但是IOS12.3(忘記版本了反正是最新的)這個方法可以執行但是是無效的。還沒有時間找找原因,這裡簡單做了個判斷如果是IOS直接使用純色填充。

if (lib.browser.os.android) {
fillStyle = context.createRadialGradient(locationX, locationY, radius, locationX + radius, locationY + radius, radius);
fillStyle.addColorStop(0, '#FFF');
fillStyle.addColorStop(1, '#FFF800');
} else {
fillStyle = '#FFF800';
}

細節三:滑雪橇的小老鼠和視差的背景(遠景、樹木和雪地)

小老鼠使用了簡單的CSS骨骼動畫( 但是我覺得沒啥區別,因為沒做很精細 ),完整的骨骼動畫至少需要將手拆成很細(如上肢、下肢、滑雪棍)、同時利用CSS做骨骼動畫有個很坑的點,就是層級,自己可以去實現下。最好使用canvas實現。至於視差其實給三個元素不同的速度即可,很簡單。篇幅很長了,這裡不展開講。

細節四:漸變的輪播的文字

為了讓文字輪播不至於很生硬,如果不加漸變,滾動的時候會出現文字裁剪的效果。 加上漸變後會讓整個過程很流暢,但是實際上要實現這個效果並不簡單。

你可以試著想一下,雪因為要不遮擋建築和文字等,所以層級會放的比較低,所以這裡的雪對應的canvas層級會比文字的層級要低,如果直接對文字容器兩端新增遮罩並設置漸變或者高斯模糊(blur)的話雖然能起到遮罩效果。但是透明度不僅針對文字,對它下面層級的元素也同樣有效果(因為這裡文字容器需要設置為透明背景)。這樣雪經過漸變的時候會出現穿透的效果,影響用戶體驗。

簡單看個效果圖(這裡我把顏色放的比較深,更容易看見效果):

[前端]實戰年終盤點

可以看到雪花被遮罩擋住了,也變得有漸變了。

[前端]實戰年終盤點

解決方案:使用-webkit-mask-image,有興趣的可以自己去了解下:

-webkit-mask-image: linear-gradient(to bottom,
rgba(255,255,255,0) 0,
rgba(255,255,255,.6) 6%,
rgba(255,255,255,1) 25%,
rgba(255,255,255,1) 75%,
rgba(255,255,255,.6) 85%,
rgba(255,255,255,0) 100%);

細節五:steps逐幀動畫

在這次的年終盤點中充斥著很多逐幀動畫,使用animation的steps函數完成畢竟還是比較有限,我簡單寫了一個工具方法來完成我這次的效果:

var FrameAnimation = function() {
return this.initialize.apply(this, arguments);
};
FrameAnimation.prototype = {
initialize: function(frames) {
this.frames = Array.isArray(frames) ? frames : [];
this.timer = null;
this.delayTimer = null;
},
/**
* 執行完成之後的回調
* @param {Function} finish
*/
start: function(finish) {
var self = this,
frames = this.frames;
finish = typeof finish === 'function' ? finish : function() {};
if (!frames.length) {
finish();
return;
}
var runAnimation = function(frame) {
var animationFinish = function() {
if (frames.length === 0) {
finish();
return;
}
// 可能存在delay
self.delayTimer = setTimeout(function() {
self.delayTimer = null;
runAnimation(frames.shift());
}, parseInt(frame.delay, 10) || 0);
};
// 可能無法控制持續時間
if (!frame.hasOwnProperty('duration')) {
frame.animation(function() {
animationFinish();
});
} else {
var duration = parseInt(frame.duration, 10) || 0
frame.animation();
self.timer = setTimeout(function() {
self.timer = null;
animationFinish();
}, duration);
}
};
runAnimation(frames.shift());
},
/**
* 停止動畫
*/
clear: function() {
this.frames = [];
if (this.delayTimer) {
clearTimeout(this.delayTimer);
this.delayTimer = null;
}
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
}
};
return FrameAnimation;

使用如下: 雖然簡單,但是能滿足我所有的需求了。

this.enterFrame = new FrameAnimation([
// 誕我有你
{
duration: 500,
animation: function() {
self.titleUpElem.addClass("show");
}
},
// 作伴前行
{
duration: 600,
delay: 100,
animation: function() {
self.titleDownElem.addClass("show");
}
},
// 老鼠飛入
{
duration: 200,
delay: 100,
animation: function() {
self.mouseElem.addClass("show");
setTimeout(function() {
self.titleDownElem.addClass("skew");
}, 400);
}
}
]);

其實其中還有很多細節,特別是對於CSS的使用,這裡不一一列舉,畢竟篇幅很長了,也很晚了。

開發效率

這種場景切換的layers最適合了,嘿嘿…

[前端]實戰年終盤點

涉及到動畫比較多的場景的時候,也可以通過一些現有的動畫可視化工具進行參數調優,如:jeremyckahn.github.io/stylie/等。

[前端]實戰年終盤點

當然如果你只想調整貝塞爾曲線的參數: cubic-bezier也許是一個不錯的選擇。

性能

因為動畫性能的文章太多,這裡規整下,不進行深入探討。

  • 雪碧圖(尤其是動畫效果特別多的活動頁時特別重要)
  • 圖片的壓縮(你可以通過 tinypng.com/ 在線壓縮)
    [前端]實戰年終盤點
  • 視頻和音頻資源文件的壓縮(視頻初始為:15M -> 1.5M,音頻7.8M -> 851 KB)清晰度壓完覺得還是大那就抽幀吧,再不行就縮短時間。
  • 性能優化時間不夠?開啟實時幀率吧,關注掉幀
    [前端]實戰年終盤點
  • 使用transform和opacity,減少重排(reflow)和重繪(repaint)
  • 硬件加速儘量不要直接用在初始時,在開始動畫的時候再使用,使用完成後及時關掉,類似這樣,開始動畫的時候追加class,動畫完成後移除
@keyframes abcmouseMove {
0% {
transform: translate3d(0vw, 0vw, 0vw) skew(0deg);
}
25% {
/* skew一下,讓老鼠有用力的感覺 */
transform: translate3d(25vw, 0vw, 0vw) skew(-5deg);
}
50% {
transform: translate3d(50vw, 0vw, 0vw) skew(0deg);
}
100% {
transform: translate3d(100vw, 0vw, 0vw) skew(0deg);
}
}
  • 不要輕易使用will-change,除非實在沒辦法,使用完成後馬上移除掉。
  • 使用js動畫requestAnimationFrame也不要忘記了哦~
  • 不行,不想寫了,要吐了…

更多CSS性能優化深入瞭解,可以看下我們結一(結衣)老師的這篇文章搞定這些疑難雜症,向css3動畫說yes

[前端]實戰年終盤點

相關文章

巧用CSS實現酷炫的充電動畫

【Python機器學習基礎】1.人工智能、機器學習、深度學習介紹

阿里面試官讓我講講Unicode,我講了3秒說沒了,面試官說你可真菜

網頁設計中最常用的5種配圖