搖一搖遇到的問題
一、如何對搖晃效果進行反饋
剛開始的處理方式是,搖晃過程中不做任何處理,但后來反饋說這種效果不好,好像就沒有搖動一樣,如果聲音也不響的話,就真的和什么都沒發生一樣。
后來想了想,加入搖晃過程動畫,就像微信的搖一搖一樣,搖晃過程中,會有上下移動的動畫,這里加入了周圍金幣做跳躍運動的動畫。
二、搖晃不靈敏,需要用力搖晃手機才行
搖晃靈敏度是個不太好控制的量,即要求不是很靈敏,比如,不能稍微碰一下就搖晃了,也不能使勁搖才能搖中,這就需要對X、Y、Z軸上的加速度進行合理利用,這里是幾種網上常見的處理方式:
1、計算一段時間中的X、Y、Z軸的平均值:
Math.abs( x + y + z - lastX - lastY - lastZ ) / diffTime * 10000
這種方式在網上最為常見,大多數的示例都采用的該方法。后來看到有人評論此方法可能導致搖一搖並不特別靈敏,比如x為正、y為負,就可能出現抵消的情況。
2、單個方向的加速度差值滿足要求即可:
Math.abs(x-lastX) > speed || Math.abs(y-lastY) > speed
該方法也是shake庫采用的計算方式,不過,該庫的計算方式覆蓋的更全面一些,也包括了斜向搖晃的判斷,該庫有近1000個start,使用人數也較多:
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))
) {}
3、不太普遍的計算方式:
Math.sqrt(
( x - lastX ) * ( x - lastX ) +
( y - lastY ) * ( y - lastY ) +
( z - lastZ ) * ( z - lastZ )
) / diffTime * 10000
這種方式類似於求三維空間中任意兩點的距離
,然后通過距離除以時間,得到速度的變化率,在搖晃過程中搖晃的速度滿足條件就表示中獎了。目前來看,這種方式更加科學一點,因為它更能體現出搖一搖的實際情景:搖的快一點,就能搖一搖。
剛開始的時候采用的是第一種計算方式,計算在這一段時間內的平均值,但是在使用的過程中發現並不是很好用,設置的值太小就會比較靈敏,太大又不太靈敏,總是找不到一個合適的值滿足條件。后來改用第三種方式,可以得到良好的搖一搖體驗。
三、搖晃的同時讓手機振動
這個功能在業務中並沒有加入,考慮后期優化的時候添加進去,在用戶搖晃的過程中也能震動,可以很大的提升用戶體驗,讓手機震動,通過如下API:
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不支持。
四、ios手機無法主動播放音頻
這里是一篇比較完善的關於HTML Audio的說明克服 iOS HTML5 音頻的局限。
大概總結幾點:
1、兼容性
iOS3中,移動版safari中就已經引入HTML音頻
iOS6中,Apple增加了Web Audio API的支持
備注:到目前為止,iOS版本分布如下,所以,目前使用HTML音頻基本沒有兼容問題。


格式支持
目前主要支持四種格式:MP3、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
這個問題還沒有找到具體原因。應該和服務器設置有關,該問題已經浪費了好長時間去搜索,目前還沒找到更具體的原因。
五、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](Javascript date parsing on Iphone)有人提這個問題,這是一個回答:並不是所有的瀏覽器都支持上面的形式,最好的方法就是通過(
-
,,
:
)分隔符把日期分離,然后分別傳給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;
}
關於時間問題坑還是不小的,紅寶書上對時間的描述也較多,也可參考。
六、用戶交互反饋
在平時的項目中,一般要求有較快的用戶反饋,但是在搖一搖項目中,有一些小的交互需求需要注意。
第一個就是該總結剛開始說的,延遲出現抽獎結果,這樣更符合用戶體驗。搖一搖立馬彈出反而更顯突兀。
七、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 );
兼容性:

從中可以看出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; }
-
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); };
八、總結
搖一搖過程並不復雜,其實像這種活動更重要的是如何提升用戶體驗,比如在項目中發現,有的手機其支持加速事件,但是搖晃過程沒有任何的反應。比如Android 6.0; PLK-AL10(HUAWEI)
,如果有這同款手機的童鞋可以試一試。說這些是提醒有做相關活動的童鞋,可以在項目中添加統計代碼,上報該手機支持還是不支持搖一搖,如果不支持也可以加入預警,這樣可以及時得到反饋。如果不支持,可以考慮添加其他途徑也能參與活動。
關於音頻的問題,是需要繼續調研下的,如果大家知道原因麻煩也告訴我哦,查找了好幾天了。。。