H5实现摇一摇技术总结

摇一摇遇到的问题

一、如何对摇晃效果进行反馈

刚开始的处理方式是,摇晃过程中不做任何处理,但后来反馈说这种效果不好,好像就没有摇动一样,如果声音也不响的话,就真的和什么都没发生一样。

后来想了想,加入摇晃过程动画,就像微信的摇一摇一样,摇晃过程中,会有上下移动的动画,这里加入了周围金币做跳跃运动的动画。

二、摇晃不灵敏,需要用力摇晃手机才行

摇晃灵敏度是个不太好控制的量,即要求不是很灵敏,比如,不能稍微碰一下就摇晃了,也不能使劲摇才能摇中,这就需要对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 VorbisWAVPCMAAC
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有人提这个问题,这是一个回答:并不是所有的浏览器都支持上面的形式,最好的方法就是通过(-`,:`)分隔符把日期分离,然后分别传给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;
        }
  • 动画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),如果有这同款手机的童鞋可以试一试。说这些是提醒有做相关活动的童鞋,可以在项目中添加统计代码,上报该手机支持还是不支持摇一摇,如果不支持也可以加入预警,这样可以及时得到反馈。如果不支持,可以考虑添加其他途径也能参与活动。

关于音频的问题,是需要继续调研下的,如果大家知道原因麻烦也告诉我哦,查找了好几天了。。。

posted @ 2017-01-07 22:08  最骚的就是你  阅读(9462)  评论(0编辑  收藏  举报