摇一摇遇到的问题
一、如何对摇晃效果进行反馈
刚开始的处理方式是,摇晃过程中不做任何处理,但后来反馈说这种效果不好,好像就没有摇动一样,如果声音也不响的话,就真的和什么都没发生一样。
后来想了想,加入摇晃过程动画,就像微信的摇一摇一样,摇晃过程中,会有上下移动的动画,这里加入了周围金币做跳跃运动的动画。
二、摇晃不灵敏,需要用力摇晃手机才行
摇晃灵敏度是个不太好控制的量,即要求不是很灵敏,比如,不能稍微碰一下就摇晃了,也不能使劲摇才能摇中,这就需要对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音频基本没有兼容问题。
![屏幕快照 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; }
-
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)
,如果有这同款手机的童鞋可以试一试。说这些是提醒有做相关活动的童鞋,可以在项目中添加统计代码,上报该手机支持还是不支持摇一摇,如果不支持也可以加入预警,这样可以及时得到反馈。如果不支持,可以考虑添加其他途径也能参与活动。
关于音频的问题,是需要继续调研下的,如果大家知道原因麻烦也告诉我哦,查找了好几天了。。。