实现效果

本文主要是针对单图层应用的介绍,方便大家可以快速理解其中的实现原理。但在实际应用中,也就上图所示效果,定会牵扯到🏞多图层在陀螺仪下的不同方向,偏移量等视觉效果。
如果你想跳过以下过多的陈述,想直接看最终成品源码😜,可直接跳转到 功能扩展-多图层管理 目录查看🤨 源码。
知识点
📱手机陀螺仪
这里需要注意的点就是
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,
});
});
},
});
}
功能扩展
反向控制机制
- 反向控制:
- 提供两种反向模式:整体反向和单轴独立反向
js
// 创建的类文件GyroToTranslate.js
// 反向开关状态(整体反向)
this.reverse = false;
// 各轴独立反向控制(优先级高于整体反向)
this.axisReverse = {
x: false,
y: false,
z: false
};
- 使用方法:
- 应用反向处理:监听陀螺仪运动时默认调用
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轴旋转对应前后位移(可选)
};
}
多图层管理
在后期做了针对多图层管理的功能扩展👇,点击此处查看微信代码片段。
-
多图层管理 :
- 通过
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, }; }
- 通过
-
使用方法 :
- 初始化时添加所有需要控制的图层并配置基础参数
- 陀螺仪数据更新时,调用
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 });
});
},
});
},