微信小程序实现陀螺仪卡片景深效果

实现效果

本文主要是针对单图层应用的介绍,方便大家可以快速理解其中的实现原理。但在实际应用中,也就上图所示效果,定会牵扯到🏞多图层在陀螺仪下的不同方向,偏移量等视觉效果。

如果你想跳过以下过多的陈述,想直接看最终成品源码😜,可直接跳转到 功能扩展-多图层管理 目录查看🤨 源码

知识点

📱手机陀螺仪

这里需要注意的点就是x和y轴的变换,陀螺仪对应的x,y与实际css对应的rotateX,rotateY的旋转偏移是相反的。

首先通过下图理解陀螺仪旋转时所代表的x,y,z方位。

如果你想要通过js来控制和获取陀螺仪,可能会比较头大😤。因为目前安卓和ios还是存在一定差异的,而且很多浏览器也确认此行为有侵犯用户隐私的动作,且还需要授权。

如果你执意想要实现的话,可以参考本篇文章遇到问题的 解决方案,也希望能帮到你。

wx.onGyroscopeChange陀螺仪监听

以微信小程序api wx.onGyroscopeChange 为例,接口最终返回的是x,y,z轴旋转时的 角速度(rad/s)

js 复制代码
  onLoad: function () {
    // 开启陀螺仪监听
    wx.startGyroscope({
      interval: "ui",
      success: () => {
        // 监听陀螺仪数据
        this.gyroListener = wx.onGyroscopeChange((res) => {
          ...
        });
      },
    });
  }

角速度(rad/s)× 时间(s)= 旋转角度(rad)

旋转角度 × 转换系数 = 位移偏移量(px)

可以先简单理解以上的公式,发现重点就是拿到 时间差,🙄也就搞清楚了从角速度到偏移量的转换。

实现步骤

上图实现效果已经上传到了👉 微信代码片段,可以直接在开发工具上查看(⚠只能在真机上有效),此处只针对部分代码做解释说明。

  • 🔧物理关系
    • 角速度(rad/s)× 时间(s)= 旋转角度(rad)
    • 旋转角度 × 转换系数 = 位移偏移量(px)
  • 🤔核心计算步骤
    • 记录每次数据的时间戳,计算与上一次的时间差(Δt)
    • 计算角度变化:Δθ = 角速度 × Δt
    • 累积角度:θ_total = θ_total + Δθ
    • 转换为位移:translate = θ_total × 转换系数
  • 🎐关键参数调整
    • translationFactor:控制灵敏度,值越大,相同角度下位移越大
    • maxTranslation:限制最大偏移量,防止元素移出屏幕

开启陀螺仪监听

js 复制代码
  onLoad: function () {
    // 开启陀螺仪监听
    wx.startGyroscope({
      interval: "ui",
      success: () => {
        // 监听陀螺仪数据
        this.gyroListener = wx.onGyroscopeChange((res) => {
          // 转换角速度为位移偏移
          const translations = this.gyroConverter.convert(res);
          ...
        });
      },
    });
  }

创建GyroToTranslate类

1.记录时间差

陀螺仪每次变化时,执行convert方法,记录时间差,根据角速度(rad/s)× 时间差(deltaTime)= 旋转角度(rad)

js 复制代码
// 创建的类文件GyroToTranslate.js
// 上一次计算的时间戳
this.lastTime = null;
...
  /**
   * 将陀螺仪角速度转换为位移偏移量
   * @param {Object} gyroData - 陀螺仪数据 {x, y, z} 单位:rad/s
   * @returns {Object} 位移偏移量 {translateX, translateY, translateZ} 单位:px
   */
  convert(gyroData) {
    const { x, y, z} = gyroData;
    const now = Date.now();
    // 首次调用初始化时间
    if (!this.lastTime) {
      this.lastTime = now;
      return {
        translateX: 0,
        translateY: 0,
        translateZ: 0
      };
    }
    // 计算时间差(秒)
    const deltaTime = (now - this.lastTime) / 1000;
    this.lastTime = now;
    // 计算角度变化(角速度 × 时间 = 角度变化量)
    const deltaAngle = {
      x: x * deltaTime,
      y: y * deltaTime,
      z: z * deltaTime
    };
    ...
  }

2.记录累计旋转角度

js 复制代码
// 创建的类文件GyroToTranslate.js
    // 累计的旋转角度(弧度)
    this.accumulatedAngle = {
      x: 0,
      y: 0,
      z: 0
    };
    ...
    // 计算角度变化(角速度 × 时间 = 角度变化量)
    const deltaAngle = {
      x: x * deltaTime,
      y: y * deltaTime,
      z: z * deltaTime
    };
    // 累计角度(积分过程)
    this.accumulatedAngle.x += deltaAngle.x;
    this.accumulatedAngle.y += deltaAngle.y;
    this.accumulatedAngle.z += deltaAngle.z;

3.计算位移偏差

js 复制代码
// 创建的类文件GyroToTranslate.js
    // 位移转换系数(角度每弧度对应的像素偏移)
    this.translationFactor = 60; // 可调整,值越大位移越灵敏
    // 最大偏移限制(防止过度偏移)
    this.maxTranslation = 200;
    ...
     // 计算位移并应用反向和精度控制
    const calculateTranslation = (angle, axis) => {
     let translation = angle * this.translationFactor;
     // 限制最大偏移
     translation = Math.max(-this.maxTranslation, Math.min(this.maxTranslation, translation));
     // 应用精度处理
     return this.roundToPrecision(translation);
    };

    return {
      translateX: calculateTranslation(this.accumulatedAngle.y, 'x'), // y轴旋转对应左右位移
      translateY: calculateTranslation(-this.accumulatedAngle.x, 'y'), // x轴旋转对应上下位移
      translateZ: calculateTranslation(this.accumulatedAngle.z, 'z') // z轴旋转对应前后位移(可选)
    };

style样式赋值

html 复制代码
<view class="app-container">
  <view class="container">
    <view class="dot" style="transform: translateX({{translateX}}rpx) translateY({{translateY}}rpx) translateZ({{translateZ}}rpx);">1</view>
    <view style="margin-top: 50rpx;">x轴角速度:{{p.x}}</view>
    <view>y轴角速度:{{p.y}}</view>
    <view>z轴角速度:{{p.z}}</view>
    <view style="margin-top: 50rpx;">translateX偏移量:{{translateX}}</view>
    <view>translateY偏移量:{{translateY}}</view>
    <view>translateZ偏移量:{{translateZ}}</view>
  </view>
</view>
js 复制代码
// 引入封装好的GyroToTranslate类
const GyroToTranslate = require("../../utils/GyroToTranslate.js");
...
  data: {
    p: {
      x: 0,
      y: 0,
      z: 0,
    },
    translateX: 0,
    translateY: 0,
    translateZ: 0,
  },
  onLoad: function () {
    // 初始化转换器
    this.gyroConverter = new GyroToTranslate();
    // 开启陀螺仪监听
    wx.startGyroscope({
      interval: "ui",
      success: () => {
        // 监听陀螺仪数据
        this.gyroListener = wx.onGyroscopeChange((res) => {
          // 转换角速度为位移偏移
          const translations = this.gyroConverter.convert(res);
          // 更新UI
          this.setData({
            p: res,
            translateX: translations.translateX,
            translateY: translations.translateY,
            translateZ: translations.translateZ,
          });
        });
      },
    });
  }

功能扩展

反向控制机制

  1. 反向控制
  • 提供两种反向模式:整体反向和单轴独立反向
js 复制代码
// 创建的类文件GyroToTranslate.js
    // 反向开关状态(整体反向)
    this.reverse = false;
    // 各轴独立反向控制(优先级高于整体反向)
    this.axisReverse = {
      x: false,
      y: false,
      z: false
    };
  1. 使用方法
  • 应用反向处理:监听陀螺仪运动时默认调用applyReverse应用反向的初始属性
  • 整体反向:调用setReverse(true)开启整体反向
  • 单轴反向:调用setAxisReverse('x', true)开启 X 轴反向(x/y/z 可选)
  • 可以在 UI 上添加开关组件,通过事件绑定控制这些方法
js 复制代码
  /**
   * 设置整体反向开关
   * @param {boolean} isReverse - 是否反向
   */
  setReverse(isReverse) {
    this.reverse = isReverse;
  }

  /**
   * 设置单个轴的反向开关
   * @param {string} axis - 轴名称 x/y/z
   * @param {boolean} isReverse - 是否反向
   */
  setAxisReverse(axis, isReverse) {
    if (['x', 'y', 'z'].includes(axis)) {
      this.axisReverse[axis] = isReverse;
    }
  }
  ...
   /**
   * 应用反向处理
   * @param {number} value - 原始值
   * @param {string} axis - 轴名称
   * @returns {number} 处理后的值
   */
  applyReverse(value, axis) {
    // 轴独立反向优先于整体反向
    if (this.axisReverse[axis]) {
      return -value;
    }
    // 应用整体反向
    return this.reverse ? -value : value;
  }
    /**
   * 将陀螺仪角速度转换为位移偏移量
   * @param {Object} gyroData - 陀螺仪数据 {x, y, z} 单位:rad/s
   * @returns {Object} 位移偏移量 {translateX, translateY, translateZ} 单位:px
   */
  convert(gyroData) {
    const { x, y, z } = gyroData;
    ...
    // 计算位移并应用反向和精度控制
    const calculateTranslation = (angle, axis) => {
      let translation = angle * this.translationFactor;
      // 应用反向设置
      translation = this.applyReverse(translation, axis);
      ...
    };
    return {
      translateX: calculateTranslation(this.accumulatedAngle.y, 'x'), // y轴旋转对应左右位移
      translateY: calculateTranslation(-this.accumulatedAngle.x, 'y'), // x轴旋转对应上下位移
      translateZ: calculateTranslation(this.accumulatedAngle.z, 'z') // z轴旋转对应前后位移(可选)
    };
  }

多图层管理

在后期做了针对多图层管理的功能扩展👇,点击此处查看微信代码片段。

  1. 多图层管理

    • 通过addLayer方法添加任意数量的图层,每个图层使用唯一 ID 标识
    • 可以为每个图层单独设置转换系数(factor),值越大移动越灵敏
    • 支持为每个图层设置最大偏移限制(max),防止过度偏移
    js 复制代码
    // 图层配置,key为图层ID,value为配置
    this.layers = {};
    // 位移转换系数(角度每弧度对应的像素偏移)
    this.factor = 60; // 可调整,值越大位移越灵敏
    // 最大偏移限制(防止过度偏移)
    this.max = 200;
    ...
    /**
    * 添加图层配置
    * @param {string} layerId - 图层唯一标识
    * @param {Object} config - 图层配置
    * @param {number} config.factor - 位移转换系数(默认20)
    * @param {number} config.max - 最大偏移限制(默认200)
    * @param {boolean} config.reverseX - X轴反向(默认false)
    * @param {boolean} config.reverseY - Y轴反向(默认false)
    * @param {boolean} config.reverseZ - Z轴反向(默认false)
    */
    addLayer(layerId, config = {}) {
    this.layers[layerId] = {
     factor: config.factor || this.factor,
     max: config.max || this.max,
     reverseX: config.reverseX || false,
     reverseY: config.reverseY || false,
     reverseZ: config.reverseZ || false,
     ...config,
     };
    }
  2. 使用方法

    • 初始化时添加所有需要控制的图层并配置基础参数
    • 陀螺仪数据更新时,调用updateAllLayers获取所有图层的位移数据
html 复制代码
<view class="app-container">
  <view class="gyro ren-wu" style="transform: translateX({{layers.renwu.translateX}}rpx) translateY({{layers.renwu.translateY}}rpx) translateZ({{layers.renwu.translateZ}}rpx) scale(1);left: -3%;">
    <image mode="heightFix" src="../../image/xue-ren.jpg" alt="" />
  </view>
  <view class="gyro xue-hua" style="transform: translateX({{layers.xuehua.translateX}}rpx) translateY({{layers.xuehua.translateY}}rpx) translateZ({{layers.xuehua.translateZ}}rpx) scale(1.3);">
    <image src="../../image/xue-hua.jpg" alt="" />
  </view>
  <view class="gyro yang-guang" style="transform: translateX({{layers.yanghuang.translateX}}rpx) translateY({{layers.yanghuang.translateY}}rpx) translateZ({{layers.yanghuang.translateZ}}rpx);">
    <image src="../../image/yang-guang.jpg" alt="" />
  </view>
  <view class="gyro guang-xian" style="transform: translateX({{layers.guangxian.translateX}}rpx) translateY({{layers.guangxian.translateY}}rpx) translateZ({{layers.guangxian.translateZ}}rpx) scale(1.2);">
    <image src="../../image/guang-xian.jpg" alt="" />
  </view>
</view>
js 复制代码
  onShow: function () {
    // 初始化转换器
    this.gyroController = new GyroToTranslate();
    // 添加图层配置 - 可以根据需要添加任意多个图层
    this.gyroController.addLayer("xuehua", {
      factor: 100,
      max: 300,
    });
    ...
    this.gyroController.addLayer("guangxian", {
      factor: 100,
      max: 200,
      reverseX: true,
      reverseY: true,
    });
    // 初始化图层数据
    this.setData({
      layers: this.gyroController.getInitialPositions(),
    });
    // 开启陀螺仪监听
    wx.startGyroscope({
      interval: "ui",
      success: () => {
        // 监听陀螺仪数据
        this.gyroListener = wx.onGyroscopeChange((res) => {
          // 更新所有图层位移
          const layerTranslations = this.gyroController.updateAllLayers(res);
          // console.log(layerTranslations);
          this.setData({ layers: layerTranslations });
        });
      },
    });
  },

参考文章

相关推荐
徐小夕3 分钟前
开源办公神器OfficeHub:文档、表格、AI 于一体,还能搭知识库!
前端·vue.js·github
Data_Adventure20 分钟前
能连上 GitHub(SSH 验证成功),却 push 失败?常见原因与逐步解决方案
前端·git·github
勒是山城雾都33 分钟前
应用驾驶舱接入金融机构前端规范文档
前端
哈撒Ki34 分钟前
简单理解:序列化与反序列化
前端
鲸鱼呀35 分钟前
vue3+ant-design-vue4.x+sortablejs 实现可拖拽行表格
前端
用户14095081128036 分钟前
观察者模式 vs 发布订阅模式
前端
Tony小周37 分钟前
qml 实现数值键盘
前端·javascript·html
程序员海军1 小时前
不要太信任Cursor,这位网友被删库了。。。
前端·后端·cursor
mini_0551 小时前
vue3,使用v-draggable拖动时卡顿的问题
前端·javascript·vue.js
Mintopia1 小时前
Next.js 服务端状态管理:React Query vs SWR(强烈推荐)
前端·javascript·next.js