时间:2023-07-23 08:54:02 | 来源:网站运营
时间:2023-07-23 08:54:02 来源:网站运营
不到30行代码实现一个酷炫H5全景:前言: 本文将围绕:了解什么是全景 --> 怎么构成全景 --> 全景交互原理来进行讲解,手把手教你从零基础实现一个酷炫的 Web 全景,并讲解其中的原理。小白也能学习,建议收藏学习,有任何疑问,请在评论区讨论,笔者经常查看并回复。实现方式 | 费用 | 是否开源 | 学习成本 | 开发难度 | 兼容性 | 扩展 | 性能 |
---|---|---|---|---|---|---|---|
CSSS 3D | 免费 | 是 | 中 | 难 | 支持 CSS3D 的浏览器 | 易 | 低 |
ThreeJS | 免费 | 是 | 高 | 中 | 支持 WebGL 的部分浏览器 | 易 | 高 |
全景工具(Krpano) | 收费 | 否 | 易 | 无 | 支持 flash 和 canvas 的浏览器 | 难 | 中 |
球体全景所需的图片素材(下图):宽是高的两倍,数值是 2 的整数倍最好,建议图片宽高为 2048px*1024px(后面实现全景会用到哈)
<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <title>手把手教你制作酷炫Web全景</title> <meta name="viewport" id="viewport" content="width=device-width,initial-scale=1,minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover" /> </head> <body> <div id="wrap" style="position: absolute;z-index: 0;top: 0;bottom: 0;left: 0;right: 0;width: 100%;height: 100%;overflow: hidden;" ></div> <script src="https://cdn.bootcdn.net/ajax/libs/three.js/r128/three.js"></script> <script> const width = window.innerWidth const height = window.innerHeight const radius = 50 // 球体半径 // 第一步:创建场景 const scene = new THREE.Scene() // 第二步:绘制一个球体 const geometry = new THREE.SphereBufferGeometry(radius, 32, 32) const material = new THREE.MeshBasicMaterial({ map: new THREE.TextureLoader().load('./img/1.jpeg'), // 上面的全景图片,注意引用目录 }) const mesh = new THREE.Mesh(geometry, material) scene.add(mesh) // 第三步:创建相机,并确定相机位置 const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000) camera.position.x = 0 // 确定相机位置 camera.position.y = 0 camera.position.z = radius * 3 // 走远了看 camera.target = new THREE.Vector3(0, 0, 0) // 设定对焦点 // 第四步:拍照并绘制到canvas const renderer = new THREE.WebGLRenderer() renderer.setSize(width, height) // 设置照片大小 document.querySelector('#wrap').appendChild(renderer.domElement) // 绘制到canvas function render() { camera.lookAt(camera.target) // 对焦 renderer.render(scene, camera) // 拍照 // 不断渲染,因为图片加载和处理需要时间,不确定何时拍照合适 requestAnimationFrame(render) } render() </script> </body></html>
浏览器页面效果(记得开启手机模拟调试):<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <title>手把手教你制作酷炫Web全景</title> <meta name="viewport" id="viewport" content="width=device-width,initial-scale=1,minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover" /> </head> <body> <div id="wrap" style="position: absolute;z-index: 0;top: 0;bottom: 0;left: 0;right: 0;width: 100%;height: 100%;overflow: hidden;" ></div> <script src="https://cdn.bootcdn.net/ajax/libs/three.js/r128/three.js"></script> <script> const width = window.innerWidth, height = window.innerHeight // 屏幕宽高 const radius = 50 // 球体半径 // 第一步:创建场景 const scene = new THREE.Scene() // 第二步:绘制一个球体 const geometry = new THREE.SphereBufferGeometry(radius, 32, 32) geometry.scale(-1, 1, 1) // 球面反转,由外表面改成内表面贴图 const material = new THREE.MeshBasicMaterial({ map: new THREE.TextureLoader().load('./img/1.jpeg'), // 下载上面的全景图片到./img目录下 }) const mesh = new THREE.Mesh(geometry, material) scene.add(mesh) // 第三步:创建相机,并确定相机位置 const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000) camera.position.x = 0 // 确定相机位置移到球心 camera.position.y = 0 camera.position.z = 0 camera.target = new THREE.Vector3(radius, 0, 0) // 设置一个对焦点 // 第四步:拍照并绘制到canvas const renderer = new THREE.WebGLRenderer() renderer.setPixelRatio(window.devicePixelRatio) renderer.setSize(width, height) // 设置照片大小 document.querySelector('#wrap').appendChild(renderer.domElement) // 绘制到canvas renderer.render(scene, camera) let lat = 0, lon = 0 function render() { lon += 0.003 // 每帧加一个偏移量 // 改变相机的对焦点,计算公式参考:2.2.2章节 camera.target.x = radius * Math.cos(lat) * Math.cos(lon) camera.target.y = radius * Math.sin(lat) camera.target.z = radius * Math.cos(lat) * Math.sin(lon) camera.lookAt(camera.target) // 对焦 renderer.render(scene, camera) requestAnimationFrame(render) } render() </script> </body></html>
效果:distanceX = clientX1 - clientX2 // X轴方向distanceY = clientY1 - clientY2 // Y轴方向// 其中R为球体半径,根据弧长公式:lon = distanX / Rlat = distanY / R
代码实现:// 增加touch事件监听let lastX, lastY // 上次屏幕位置let curX, curY // 当前屏幕位置const factor = 1 / 10 // 灵敏系数const $wrap = document.querySelector('#wrap')// 触摸开始$wrap.addEventListener('touchstart', function (evt) { const obj = evt.targetTouches[0] // 选择第一个触摸点 startX = lastX = obj.clientX startY = lastY = obj.clientY})// 触摸中$wrap.addEventListener('touchmove', function (evt) { evt.preventDefault() const obj = evt.targetTouches[0] curX = obj.clientX curY = obj.clientY // 参考:弧长公式 lon -= ((curX - lastX) / radius) * factor // factor为了全景旋转平稳,乘以一个灵敏系数 lat += ((curY - lastY) / radius) * factor lastX = curX lastY = curY})
单指操作效果: let lastX, lastY // 上次屏幕位置let curX, curY // 当前屏幕位置let startX, startY // 开始触摸的位置,用于计算速度let isMoving = false // 是否停止单指操作let speedX, speedY // 速度const factor = 1 / 10 // 灵敏系数,经验值const deceleration = 0.1 // 减速度,惯性动画使用const $wrap = document.querySelector('#wrap')// 触摸开始$wrap.addEventListener('touchstart', function (evt) { const obj = evt.targetTouches[0] // 选择第一个触摸点 startX = lastX = obj.clientX startY = lastY = obj.clientY startTime = Date.now() isMoving = true})// 触摸中$wrap.addEventListener('touchmove', function (evt) { evt.preventDefault() const obj = evt.targetTouches[0] curX = obj.clientX curY = obj.clientY // 参考:弧长公式 lon -= ((curX - lastX) / radius) * factor // factor为了全景旋转平稳,乘以一个系数 lat += ((curY - lastY) / radius) * factor lastX = curX lastY = curY})// 触摸结束$wrap.addEventListener('touchend', function (evt) { isMoving = false var t = Date.now() - startTime speedX = (curX - startX) / t // X轴方向的平均速度 speedY = (curY - startY) / t // Y轴方向的平均速度 subSpeedAnimate() // 惯性动画})let animateInt// 减速度动画function subSpeedAnimate() { lon -= speedX * factor // X轴 lat += speedY * factor // 减速度 speedX = subSpeed(speedX) speedY = subSpeed(speedY) // 速度为0或者有新的触摸事件,停止动画 if ((speedX === 0 && speedY === 0) || isMoving) { if (animateInt) { cancelAnimationFrame(animateInt) animateInt = undefined } } else { requestAnimationFrame(subSpeedAnimate) }}// 减速度function subSpeed(speed) { if (speed !== 0) { if (speed > 0) { speed -= deceleration; speed < 0 && (speed = 0); } else { speed += deceleration; speed > 0 && (speed = 0); } } return speed;}
预览地址:https://azuoge.github.io/Opanorama/const camera = new THREE.PerspectiveCamera(fov, aspect, near, fear)
参数说明:// 其中,(clientX1,clientY1)和(clientX2,clientY2)为双指在屏幕的当前位置// 计算距离,简化运输不用平方计算const distance = Math.abs(clientX1 - clientX2) + Math.abc(clientY1 - clientY)// 计算缩放比const scale = distance / lastDiance// 计算新的视角fov = camera.fov / scale// 视角范围取值camera.fov = Math.min(90, Math.max(fov, 60)) // 90 > fov > 60 ,从参数说明中选取// 视角需要主动更新camera.updateProjectionMatrix()
体验地址:https://azuoge.github.io/Opanorama/// 角度换算弧度公式const L = Math.PI / 180// 陀螺仪交互window.addEventListener('deviceorientation', function (evt) { lon = evt.alpha * L lat = (evt.beta - 90) * L})
效果如下:lat = touch.lat + orienter.lat + fix.lat // 取值范围:[-90,90]lon = touch.lon + orienter.lon + fix.lon // 取值范围:[0,360]
其中,touch 为手势影响,orienrer 为陀螺仪影响,fix 为修正因子,保证经纬度在换算的结果始终符合取值范围。关键词:实现