H5實現搖一搖技術總結

搖一搖遇到的問題

1、如何對搖晃效果進行反饋

剛開始的處理方式是,搖晃過程當中不作任何處理,但後來反饋說這種效果很差,好像就沒有搖動同樣,若是聲音也不響的話,就真的和什麼都沒發生同樣。javascript

後來想了想,加入搖晃過程動畫,就像微信的搖一搖同樣,搖晃過程當中,會有上下移動的動畫,這裏加入了周圍金幣作跳躍運動的動畫。css

squire

2、搖晃不靈敏,須要用力搖晃手機才行

搖晃靈敏度是個不太好控制的量,即要求不是很靈敏,好比,不能稍微碰一下就搖晃了,也不能使勁搖才能搖中,這就須要對X、Y、Z軸上的加速度進行合理利用,這裏是幾種網上常見的處理方式:html

一、計算一段時間中的X、Y、Z軸的平均值:

Math.abs( x + y + z - lastX - lastY - lastZ ) / diffTime * 10000

這種方式在網上最爲常見,大多數的示例都採用的該方法。後來看到有人評論此方法可能致使搖一搖並不特別靈敏,好比x爲正、y爲負,就可能出現抵消的狀況。html5

二、單個方向的加速度差值知足要求便可:

Math.abs(x-lastX) > speed || Math.abs(y-lastY) > speed

該方法也是shake庫採用的計算方式,不過,該庫的計算方式覆蓋的更全面一些,也包括了斜向搖晃的判斷,該庫有近1000個start,使用人數也較多:java

this.options.threshold = 15;
deltaX = Math.abs(this.lastX - current.x);
deltaY = Math.abs(this.lastY - current.y);
deltaZ = Math.abs(this.lastZ - current.z);
if ((
    (deltaX > this.options.threshold) && 
    (deltaY > this.options.threshold)) || 
    (
        (deltaX > this.options.threshold) && 
        (deltaZ > this.options.threshold)
    ) || 
    (
        (deltaY > this.options.threshold) && 
        (deltaZ > this.options.threshold))
    ) {}

三、不太廣泛的計算方式:

Math.sqrt( 
    ( x - lastX ) * ( x - lastX ) + 
    ( y - lastY ) * ( y - lastY ) + 
    ( z - lastZ ) * ( z - lastZ ) 
) / diffTime * 10000

這種方式相似於求三維空間中任意兩點的距離,而後經過距離除以時間,獲得速度的變化率,在搖晃過程當中搖晃的速度知足條件就表示中獎了。目前來看,這種方式更加科學一點,由於它更能體現出搖一搖的實際情景:搖的快一點,就能搖一搖。android

剛開始的時候採用的是第一種計算方式,計算在這一段時間內的平均值,可是在使用的過程當中發現並非很好用,設置的值過小就會比較靈敏,太大又不太靈敏,老是找不到一個合適的值知足條件。後來改用第三種方式,能夠獲得良好的搖一搖體驗。ios

3、搖晃的同時讓手機振動

這個功能在業務中並無加入,考慮後期優化的時候添加進去,在用戶搖晃的過程當中也能震動,能夠很大的提高用戶體驗,讓手機震動,經過以下API:css3

navigator.vibrate

特徵檢測:

var supportsVibrate = "vibrate" in navigator;

使用方法:

navigator.vibrate(2000) // 震動2s
window.navigator.vibrate([
    100,30,100,30,100,200,200,30,
        200,30,200,200,100,30,100,30,100]); // 震動出莫爾斯電碼的"SOS"效果

// 取消震動,賦值0或空數組便可
navigator.vibrate(0)

經過CanIuse查看支持狀況,支持android4.4,ios不支持。git

QQ20170105-001330

4、ios手機沒法主動播放音頻

這裏是一篇比較完善的關於HTML Audio的說明克服 iOS HTML5 音頻的侷限github

大概總結幾點:

一、兼容性

iOS3中,移動版safari中就已經引入HTML音頻

iOS6中,Apple增長了Web Audio API的支持

備註:到目前爲止,iOS版本分佈以下,因此,目前使用HTML音頻基本沒有兼容問題。

屏幕快照 2016-12-23 下午2.19.59

屏幕快照 2016-12-23 下午2.23.04

格式支持

目前主要支持四種格式:MP三、OGG、WAV 和 AAC。

Ogg Vorbis WAV PCM AAC
Internet Explorer 9 X X
Firefox X X
Chrome/Safari/移動版 Safari X X X

爲了涵蓋全部瀏覽器,最好是讓全部的視頻流都具備 Ogg Vorbis 和 AAC 兩種格式。

爲何沒有包括 MP3?MP3 在進行商業傳播時須要支付繁重的版稅。

Ogg Vorbis 之因此壓倒性地得到了個人喜好是由於它是開源的、無專利費而且免版稅的。不過,只有 Firefox 支持它。

單音頻流

移動版safari一次只能播放一個單音頻流。移動版 Safari 中的 HTML5 媒體元素都是單例的,因此一次只能播放一個 HTML5 音頻(和 HTML5 視頻)流。Apple 爲這一侷限作過解釋,但咱們推斷這是爲了減小數據費用(這也是大多數 iOS HTML5 其餘侷限的緣由所在)。

自動播放

在移動版 Safari 中加載的頁面上,不能自動播放音頻文件。音頻文件只能從用戶觸發的觸摸(單擊)事件加載。preload、autoplay會忽略。

切換延遲

在初始化一個新的音頻流時會有幾秒的延時,這是由於 iOS 須要實例化一個新的音頻對象。

解決方案

解決自動播放

當用戶觸摸屏幕的時候,便會加載音頻,而後在搖晃手機時進行播放。

var shakeAudio = new Audio();
shakeAudio.preload = 'auto';
shakeAudio.src = 'xx';
document.body.addEventListener('touchstart', () => {
    shakeAudio.load();
});

解決單音頻流 && 解決切換延遲

使用audio sprite

audio.sprite能夠將多個音頻合成一個音頻,就像css sprite同樣。

原理很直觀。您須要存儲每一個 sprite 的數據:開始點、結束點(或長度)和一個 ID。當您想要播放某個 sprite 時,須要將此音頻流的 currentTime 設爲開始位置並調用 play()。

var spriteData = {
    meow1: {
        start: 0,
        length: 1.1
    },
    meow2: {
        start: 1.3,
        length: 1.1
    },
    whine: {
        start: 2.7,
        length: 0.8
    },
    purr: {
        start: 5,
        length: 5
    }
};
audioSprite.currentTime = spriteData.meow2.start;
audioSprite.play();

這種方式能夠解決單個音頻與切換延遲的問題,由於只有一個音頻加載,這也削減了HTTP請求。

清注意,更改 currentTime 並非百分百正確的。將 currentTime 設爲 6.5,而實際獲得的倒是 6.7 或 6.2。每一個 A sprite 之間須要少許的空間,以免尋找到另外一個 sprite 的尾部。添加這個空間會增長少量延時,若是流尋找到 6.4,而 sprite 開始於 6.8 秒。

在訪問任何 audio sprite 以前,務必確保整個音頻流已加載,由於若是音頻流沒有徹底加載,那麼在想要訪問已加載的流的任何一個部分時,那麼這個流須要進行緩衝,並且還會在流加載過程當中發生延時。

注意,音頻資源放到服務器可能也不會成功!

這是Chromium的一個bug:https://bugs.chromium.org/p/chromium/issues/detail?id=584562

備註:我在實際測試的出現問題,currentTime沒法設置,一直都是0,即時賦值爲3,但打印出來後依然爲0,致使音頻不能切換。

測試地址:http://img.youthol.top/audioTest-1.html

這個問題尚未找到具體緣由。應該和服務器設置有關,該問題已經浪費了好長時間去搜索,目前還沒找到更具體的緣由。

5、ios手機Date兼容問題

背景:

在項目中有一個體驗須要優化,若是活動在晚上12點開始,用戶在12點以前進來,此時是不能參加的,提示活動時間還不到,可是若是到12點了呢?用戶必須退出去而後在進來,這樣才能看到最新的狀態?

這樣體驗會差一點,最好的方式是:到達了12點,數據自動更新。用戶能夠直接參與抽獎!

實現思路:

爲了實現這一個效果,採用的思路是:用戶剛進入頁面的時候會有一個時間戳,而後我拿到該時間戳進行解析,獲取當天的時間屬性(年月日),而後經過new Date()建立一個次日的凌晨時間,此時,用該時間與頁面請求的時間作diff,利用setTimeout進行diff時間的倒計時,倒計時結束,自動執行數據更新。

遇到的問題

這種思路在android上是沒有問題的,可是測試時發如今ios上不會更新數據,後來定時,是獲取時間時有問題:

原本我是經過new Date("2016.12.27")這樣的方式在chrome的測試控制檯中測試的,能夠拿到當天凌晨時間,因而便拿該時間去用,可是該時間在safari中會出錯:

new Date("2016.12.27")
// Invalid Date
new Date("2016.12.27").getTime()
// NaN

這就遇到了一個好玩的事情,那safari下支持什麼樣的時間格式呢?下面便進行一些測試:

刨坑

safari:在safari瀏覽器中處理時間會產生很是有意思的效果,好比:

new Date("2016.12.27")
// Invalid Date

new Date("2016-12-27")
// Tue Dec 27 2016 08:00:00 GMT+0800 (CST)

new Date("2016/12/27")
// Tue Dec 27 2016 00:00:00 GMT+0800 (CST)

chrome:可是在chrome上,咱們測試一下:

new Date("2016.12.27")
// Tue Dec 27 2016 00:00:00 GMT+0800 (CST)

new Date("2016-12-27")
// Tue Dec 27 2016 08:00:00 GMT+0800 (CST)

new Date("2016/12/27")
// Tue Dec 27 2016 00:00:00 GMT+0800 (CST)

. 點的日期形式在safari上是不支持的

- 短線的日期形式返回值相同

/ 斜槓的日期形式返回值也相同

另一個更神奇的地方是,一樣是ISO 8601日期格式形式,safari也獲取不到:

new Date("2016-12-27 12:34:25")
// Invalid Date

stackoverflow有人提這個問題,這是一個回答:並非全部的瀏覽器都支持上面的形式,最好的方法就是經過(-,` :`)分隔符把日期分離,而後分別傳給Date構造器:

var arr = "2010-03-15 10:30:00".split(/[- :]/),
date = new Date(arr[0], arr[1]-1, arr[2], arr[3], arr[4], arr[5]);

console.log(date);
//-> Mon Mar 15 2010 10:30:00 GMT+0000 (GMT Standard Time)

這樣全部的瀏覽器都運行正常。不過,須要注意的是,月份要減1,GMT的時間月份0表示1月份。

一樣,根據這篇文章:JavaScript new Date() NaN on iPhone,能夠經過以下方式,也能夠獲取瀏覽器一致的效果:

mm/dd/yyyy hh:mm:ss

if (app.isAppleDevice()) {
    var dateParts = myDate.substring(0,10).split('-');
    var timePart = myDate.substr(11);
    myDate= dateParts[1] + '/' + dateParts[2] + '/' + dateParts[0] + ' ' + timePart;
}

關於時間問題坑仍是不小的,紅寶書上對時間的描述也較多,也可參考。

6、用戶交互反饋

在平時的項目中,通常要求有較快的用戶反饋,可是在搖一搖項目中,有一些小的交互需求須要注意。

第一個就是該總結剛開始說的,延遲出現抽獎結果,這樣更符合用戶體驗。搖一搖立馬彈出反而更顯突兀。

7、requestAnimationFrame

在業務中,有一個滾動公告需求,剛開始滾動公告採用setInterval的方式進行,可是當把切換其餘瀏覽器tab一段時候後再次回來,發現公告是快速的滾動到某一位置。

緣由是setInterval在窗口退到後臺時依然會執行。解決這個問題就須要requestAnimationFrame了。

  • requestAnimationFrame是用來解決動畫渲染問題的,通常來講,其渲染執行頻率和瀏覽器渲染頻率相同,都爲60幀。
  • requestAnimationFrame發生在重繪前,瀏覽器在重繪時會提早通知requestAnimationFrame,這樣咱們能夠把DOM操做集中在一塊兒,這樣只發生一次重繪。
  • 隱藏或不可見元素,requestAnimationFrame將不進行重繪或迴流。減小內存使用量。

雖然requestAnimationFrame不能夠直接設置時間間隔,但能夠經過時間判斷來完成:

let lastTime = 0;
const scroll = () => {
    const now = Date.now();
    if ( now - startTime > during ) {
        startTime = now;
        this.shakeScrollCurrent--;
        this.showNoticeList = true;
        // 若是滾動到頭了,此時會從新進入滾動
        // 防止在切換的過程當中出現動畫,此時須要先把動畫去除,而後在進行數量重置
        if ( ( this.shakeScrollCurrent ) % ( prizesLength + 1 ) === 0 ) {
            this.showNoticeList = false;
            this.shakeScrollCurrent = 0;
        }
    }
    window.requestAnimationFrame( scroll );
}
window.requestAnimationFrame( scroll );

兼容性:

屏幕快照 2017-01-05 下午3.08.19

從中能夠看出android低版本(4.3)及如下是不支持該屬性的,須要對此進行兼容,能夠參考以下:

  • CSS3動畫那麼強,requestAnimationFrame還有毛線用?

    window.requestAnimationFrame = window.requestAnimationFrame ||
          window.webkitRequestAnimationFrame ||
          window.mozRequestAnimationFrame ||
          window.oRequestAnimationFrame ||
          window.msRequestAnimationFrame || function ( callback, element ) {
              var currTime = new Date().getTime();
              var timeToCall = Math.max(0, 16.7 - (currTime - lastTime));
              var id = window.setTimeout(function() {
                  callback(currTime + timeToCall);
              }, timeToCall);
              lastTime = currTime + timeToCall;
              return id;
          }
  • 動畫requestAnimationFrame

    function (callback, element) {
          var start, finish;
          window.setTimeout(function () {
              start = +new Date();
              callback(start);
              finish = +new Date();
              self.timeout = 1000 / 60 - (finish - start);
          }, self.timeout);
      };

8、總結

搖一搖過程並不複雜,其實像這種活動更重要的是如何提高用戶體驗,好比在項目中發現,有的手機其支持加速事件,可是搖晃過程沒有任何的反應。好比Android 6.0; PLK-AL10(HUAWEI),若是有這同款手機的童鞋能夠試一試。說這些是提醒有作相關活動的童鞋,能夠在項目中添加統計代碼,上報該手機支持仍是不支持搖一搖,若是不支持也能夠加入預警,這樣能夠及時獲得反饋。若是不支持,能夠考慮添加其餘途徑也能參與活動。

關於音頻的問題,是須要繼續調研下的,若是你們知道緣由麻煩也告訴我哦,查找了好幾天了。。。

相關文章
相關標籤/搜索