H5實現搖一搖技術總結


搖一搖遇到的問題

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

剛開始的處理方式是,搖晃過程中不做任何處理,但后來反饋說這種效果不好,好像就沒有搖動一樣,如果聲音也不響的話,就真的和什么都沒發生一樣。

后來想了想,加入搖晃過程動畫,就像微信的搖一搖一樣,搖晃過程中,會有上下移動的動畫,這里加入了周圍金幣做跳躍運動的動畫。

squire

二、搖晃不靈敏,需要用力搖晃手機才行

搖晃靈敏度是個不太好控制的量,即要求不是很靈敏,比如,不能稍微碰一下就搖晃了,也不能使勁搖才能搖中,這就需要對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不支持。

QQ20170105-001330

四、ios手機無法主動播放音頻

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

大概總結幾點:

1、兼容性

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

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

備注:到目前為止,iOS版本分布如下,所以,目前使用HTML音頻基本沒有兼容問題。

![屏幕快照 2016-12-23 下午2.19.59](http://7mj4a6.com1.z0.glb.clouddn.com/2016-12-23-屏幕快照 2016-12-23 下午2.19.59.png)

![屏幕快照 2016-12-23 下午2.23.04](http://7mj4a6.com1.z0.glb.clouddn.com/2016-12-23-屏幕快照 2016-12-23 下午2.23.04.png)

格式支持

目前主要支持四種格式: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 );

兼容性:

![屏幕快照 2017-01-05 下午3.08.19](http://7mj4a6.com1.z0.glb.clouddn.com/2017-01-05-屏幕快照 2017-01-05 下午3.08.19.png)

從中可以看出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);
      };
    

八、總結

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

關於音頻的問題,是需要繼續調研下的,如果大家知道原因麻煩也告訴我哦,查找了好幾天了。。。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM