• 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),如果有这同款手机的童鞋可以试一试。说这些是提醒有做相关活动的童鞋,可以在项目中添加统计代码,上报该手机支持还是不支持摇一摇,如果不支持也可以加入预警,这样可以及时得到反馈。如果不支持,可以考虑添加其他途径也能参与活动。

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

  • 相关阅读:
    jchdl
    jchdl
    UVa 10256 (判断两个凸包相离) The Great Divide
    UVa 11168 (凸包+点到直线距离) Airport
    LA 2572 (求可见圆盘的数量) Kanazawa
    UVa 10652 (简单凸包) Board Wrapping
    UVa 12304 (6个二维几何问题合集) 2D Geometry 110 in 1!
    UVa 10674 (求两圆公切线) Tangents
    UVa 11796 Dog Distance
    LA 3263 (平面图的欧拉定理) That Nice Euler Circuit
  • 原文地址:https://www.cnblogs.com/libin-1/p/6260488.html
Copyright © 2020-2023  润新知