時間:2023-07-23 08:54:02 | 來源:網(wǎng)站運營
時間:2023-07-23 08:54:02 來源:網(wǎng)站運營
不到30行代碼實現(xiàn)一個酷炫H5全景:前言: 本文將圍繞:了解什么是全景 --> 怎么構(gòu)成全景 --> 全景交互原理來進行講解,手把手教你從零基礎(chǔ)實現(xiàn)一個酷炫的 Web 全景,并講解其中的原理。小白也能學(xué)習(xí),建議收藏學(xué)習(xí),有任何疑問,請在評論區(qū)討論,筆者經(jīng)常查看并回復(fù)。實現(xiàn)方式 | 費用 | 是否開源 | 學(xué)習(xí)成本 | 開發(fā)難度 | 兼容性 | 擴展 | 性能 |
---|---|---|---|---|---|---|---|
CSSS 3D | 免費 | 是 | 中 | 難 | 支持 CSS3D 的瀏覽器 | 易 | 低 |
ThreeJS | 免費 | 是 | 高 | 中 | 支持 WebGL 的部分瀏覽器 | 易 | 高 |
全景工具(Krpano) | 收費 | 否 | 易 | 無 | 支持 flash 和 canvas 的瀏覽器 | 難 | 中 |
球體全景所需的圖片素材(下圖):寬是高的兩倍,數(shù)值是 2 的整數(shù)倍最好,建議圖片寬高為 2048px*1024px(后面實現(xiàn)全景會用到哈)
<!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 // 球體半徑 // 第一步:創(chuàng)建場景 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) // 第三步:創(chuàng)建相機,并確定相機位置 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) // 設(shè)定對焦點 // 第四步:拍照并繪制到canvas const renderer = new THREE.WebGLRenderer() renderer.setSize(width, height) // 設(shè)置照片大小 document.querySelector('#wrap').appendChild(renderer.domElement) // 繪制到canvas function render() { camera.lookAt(camera.target) // 對焦 renderer.render(scene, camera) // 拍照 // 不斷渲染,因為圖片加載和處理需要時間,不確定何時拍照合適 requestAnimationFrame(render) } render() </script> </body></html>
瀏覽器頁面效果(記得開啟手機模擬調(diào)試):<!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 // 球體半徑 // 第一步:創(chuàng)建場景 const scene = new THREE.Scene() // 第二步:繪制一個球體 const geometry = new THREE.SphereBufferGeometry(radius, 32, 32) geometry.scale(-1, 1, 1) // 球面反轉(zhuǎn),由外表面改成內(nèi)表面貼圖 const material = new THREE.MeshBasicMaterial({ map: new THREE.TextureLoader().load('./img/1.jpeg'), // 下載上面的全景圖片到./img目錄下 }) const mesh = new THREE.Mesh(geometry, material) scene.add(mesh) // 第三步:創(chuàng)建相機,并確定相機位置 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) // 設(shè)置一個對焦點 // 第四步:拍照并繪制到canvas const renderer = new THREE.WebGLRenderer() renderer.setPixelRatio(window.devicePixelRatio) renderer.setSize(width, height) // 設(shè)置照片大小 document.querySelector('#wrap').appendChild(renderer.domElement) // 繪制到canvas renderer.render(scene, camera) let lat = 0, lon = 0 function render() { lon += 0.003 // 每幀加一個偏移量 // 改變相機的對焦點,計算公式參考:2.2.2章節(jié) 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為球體半徑,根據(jù)弧長公式:lon = distanX / Rlat = distanY / R
代碼實現(xiàn):// 增加touch事件監(jiān)聽let lastX, lastY // 上次屏幕位置let curX, curY // 當(dāng)前屏幕位置const factor = 1 / 10 // 靈敏系數(shù)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為了全景旋轉(zhuǎn)平穩(wěn),乘以一個靈敏系數(shù) lat += ((curY - lastY) / radius) * factor lastX = curX lastY = curY})
單指操作效果: let lastX, lastY // 上次屏幕位置let curX, curY // 當(dāng)前屏幕位置let startX, startY // 開始觸摸的位置,用于計算速度let isMoving = false // 是否停止單指操作let speedX, speedY // 速度const factor = 1 / 10 // 靈敏系數(shù),經(jīng)驗值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為了全景旋轉(zhuǎn)平穩(wěn),乘以一個系數(shù) lat += ((curY - lastY) / radius) * factor lastX = curX lastY = curY})// 觸摸結(jié)束$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;}
預(yù)覽地址:https://azuoge.github.io/Opanorama/const camera = new THREE.PerspectiveCamera(fov, aspect, near, fear)
參數(shù)說明:// 其中,(clientX1,clientY1)和(clientX2,clientY2)為雙指在屏幕的當(dāng)前位置// 計算距離,簡化運輸不用平方計算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 ,從參數(shù)說明中選取// 視角需要主動更新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 為修正因子,保證經(jīng)緯度在換算的結(jié)果始終符合取值范圍。關(guān)鍵詞:實現(xiàn)
微信公眾號
版權(quán)所有? 億企邦 1997-2025 保留一切法律許可權(quán)利。