H5 陀螺仪

3,439 阅读3分钟

需求

h5页面可以感应手机移动,作出相应的微动效果

实现方案

手机移动产生页面微动效果的主要用到 deviceorientation 事件,这个事件主要是监听并接收设备方向变化信息。 在window上监听 deviceorientation 事件,在回调函数中获取手机转动角度:

window.addEventListener('deviceorientation', handleFunc, false);
function handleFunc(evnet){
    var alpha = event.alpha;
    var beta = event.beta;
    var gamma = event.gamma;
 }

aplha:行动装置水平放置时,绕 Z 轴旋转的角度,数值为 0 度到 360 度。 beta:行动装置水平放置时,绕 X 轴旋转的角度,数值为 -180 度到 180 度。 gamma:行动装置水平放置时,绕 Z 轴旋转的角度,数值为 -90 度到 90 度。

具体实现

1.动画区域布局

index.js

import React from 'react'
import './index.less'

const classPrefix = 'valentine-header'
const cx = s => `${classPrefix}-${s}`

export default class ValentineHeader extends React.PureComponent {
render() {
    return <header className={classPrefix}>
                <img
                    className={cx('cloud')}
                    src={'//img.ikstatic.cn/MTU4OTI3NjIwMjIwNSMyNDUjcG5n.png'}
                    ref={el => (this.cloud = el)}
                    alt=''
                />
                <img
                    className={cx('light')}
                    src={'//img.ikstatic.cn/MTU4OTI3NjIzMTg5MSMyMTUjcG5n.png'}
                    ref={el => (this.light = el)}
                    alt=''
                />
                <img
                    className={cx('heart')}
                    src={'//img.ikstatic.cn/MTU4OTI3NjI0MzIyNiM5ODEjcG5n.png'}
                    ref={el => (this.heart = el)}
                    alt=''
                />
                <img
                    className={cx('dots')}
                    src={'//img.ikstatic.cn/MTU4OTI3NjI1NTIxMSM0NzMjcG5n.png'}
                    ref={el => (this.dots = el)}
                    alt=''
                />
                </header>
    }
}

index.less

@class-prefix: ~'valentine-header';
.@{class-prefix} {
    width: 100vw;
    height: 85vw;
    position: relative;
    overflow: hidden;
    background-color: #ffd6ee;
    height: 100vh;
    &-cloud {
        width: 187vw;
        position: absolute;
        top: -30vw;
        left: -33vw;
        z-index: 10;
    }
    &-text {
        width: 100vw;
        position: absolute;
        top: 7vw;
        z-index: 9;
    }
    &-light {
        width: 111vw;
        position: absolute;
        top: -19vw;
        left: -5.6vw;
        z-index: 2;
    }
    &-heart {
        width: 145vw;
        position: absolute;
        top: 20vw;
        left: -52vw;
        z-index: 8;
        transform: rotate(-15deg);
    }
    &-dots {
        width: 95vw;
        position: absolute;
        top: 2vw;
        left: 11vw;
        z-index: 6;
    }
}
2.监听事件

在window上监听 deviceorientation 事件,获取手动运动与方向数据,使需要动画的DOM产生对于的位置移动,为了使动画体验感更好,采取分层次动画,从上到下img元素移动的相对位置依次放大

componentDidMount() {
    window.addEventListener('deviceorientation', this.deviceorientationChange, false)
}

deviceorientationChange=(event) => {
    let { beta, gamma } = event;
    beta = Math.round(beta)
    gamma = Math.round(gamma)
    let X = Math.round(gamma / 9)
    let Y = Math.round(beta / 18- 5)

    this.cloud.style.transform = `translate(${X}px, ${Y}px)`
    this.light.style.transform = `translate(${X * 3}px, ${Y * 3}px)`
    this.heart.style.transform = `translate(${X * 4}px, ${Y * 4}px)`
    this.dots.style.transform = `translate(${X * 6}px, ${Y * 6}px)`
}

到这一步的时候安卓手机上可以查看当前动画效果了。

3.函数节流

但是很明显的感觉到,动画感应非常的灵敏,肉眼可见动画元素随着手机的移动还会产生抖动的效果。所以还需要给deviceorientation 事件做个节流:

import _throttle from 'lodash/throttle'
componentDidMount() {
    window.addEventListener('deviceorientation',_throttle(this.deviceorientationChange, 300,
    { leading: true, trailing: false }
    ), false)
}
4.动画过渡

再给所有动画的节点style加上transition:(如下所示)

&-cloud{
    width: 187vw;
    position: absolute;
    top: -30vw;
    left: -33vw;
    z-index: 10;
    transition: transform 0.5s linear;
}
5.运动边界考虑

这时动画效果就比较顺滑了,但是还有一个临界点的问题;比如gamma,手机转动至90度gamma的值会突变从90变成-90,或者-90到90。这时动画的demo会很明显的从动画范围最左边一下子闪至最右,体验感非常不好。所以需要控制运动范围,超过规定范围,我们的动画便不会再继续延伸了;

lastX = 0
lastY = 0
lastGamma = 0
lastbBeta = 0

deviceorientationChange=(event) => {
    let { beta, gamma } = event;
    beta = Math.round(beta)
    gamma = Math.round(gamma)
    let X = Math.round(gamma / 9)
    let Y = Math.round(beta / 18- 5)
    if (this.lastGamma < -50 || this.lastGamma > 50) {
        X = this.lastX
    }
    if (this.lastbBeta < -50 || this.lastbBeta > 50) {
        Y = this.lastY
    }

    this.cloud.style.transform = `translate(${X}px, ${Y}px)`
    this.light.style.transform = `translate(${X * 3}px, ${Y * 3}px)`
    this.heart.style.transform = `translate(${X * 4}px, ${Y * 4}px)`
    this.dots.style.transform = `translate(${X * 6}px, ${Y * 6}px)`

    this.lastX = X
    this.lastY = Y
    this.lastGamma = gamma
    this.lastbBeta = beta
}

iOS上使用

到这里安卓手机上的动画效果就达预期了,但是iOS手机上页面不会给出任何动画迹象。 首先,本地调试localhost我们的协议都是http,iOS12.4以上版本则必须要在https协议下才能正常使用使用陀螺仪;这里有个小技巧,可以利用Charles的mapRemote来map https to http,这样就可以在https环境下调试。 ios 13系统调用陀螺仪 需要通过window.DeviceOrientationEvent.requestPermission申请用户权限,具体移步官网; 特别需要注意的是,requestPermission方法的”首次“调用 需要 由用户交互触发如click,touchend事件等。”首次“的含义,指的是用户 完全退出app(而不是 app切换到后台运行) 后再次打开为首次。getPermission在componentDidMount里直接调用是不会执行的。

getPermission=() => {
    if (window.DeviceOrientationEvent && DeviceOrientationEvent.requestPermission) {
        DeviceOrientationEvent.requestPermission()
    }
}

欢迎指正!