本文來自飛豬前端同窗@甜蝦,教你用 N 種方式用 JS 實現一個 360 全景效果。javascript
360 全景瀏覽是一種性價比很高的虛擬現實解決方案,給人一種全新的瀏覽體驗,讓你足不出戶就能身臨其境地感覺到現場的環境。該技術被普遍地應用在房產、酒店、家居等領域。在互聯網訂房已經普及的時代,在網站上用全景展現酒店賓館的各類餐飲和住宿設施,是吸引顧客的好辦法。利用網絡,遠程虛擬瀏覽賓館的外型、大廳、客房、會議廳等各項服務場所,展示賓館溫馨的環境,給客戶以實在感覺,促進客戶預約客房。在酒店大堂提供客房的全景展現,不再用麻煩客戶在各個房間會場穿梭,就能觀看各房間的真實場景,更方便客戶確認和挑選客房,進而提升效率,用戶體驗更勝一籌。css
下面咱們使用三種方法討論一個 360 全景的實現。html
利用 CSS 中的變換、旋轉等屬性就能夠實現一個 360 全景。實現的基本思路以下:前端
下面咱們嘗試使用 div 作出一個 3D 立方體:html5
寫出 6 個面:java
<div class="scene">
<div class="cube">
<div class="cube__face cube__face--front">front</div>
<div class="cube__face cube__face--back">back</div>
<div class="cube__face cube__face--right">right</div>
<div class="cube__face cube__face--left">left</div>
<div class="cube__face cube__face--top">top</div>
<div class="cube__face cube__face--bottom">bottom</div>
</div>
</div>
複製代碼
perspective 指定觀察者與 z=0 平面的距離,使具備三維位置變換的元素產生透視效果(近大遠小原理)。transform-style 指定其爲子元素提供 2D 仍是 3D 的場景。 以下圖,高度爲 600px 的元素,距離 z=0 的屏幕 300px,視角與屏幕距離 1000px,根據類似三角形的原理,能夠計算出該元素在屏幕上的投影高度爲 857px,即實際咱們看到的元素高度。css3
關於這個屬性的詳細講解可查看css3 系列之詳解 perspective。git
寫出基本定位github
.scene {
width: 200px;
height: 200px;
perspective: 600px;
}
.cube {
width: 100%;
height: 100%;
position: relative;
transform-style: preserve-3d;
}
.cube__face {
position: absolute;
width: 200px;
height: 200px;
}
複製代碼
旋轉各面,使「三對」面互相垂直,達到以下效果web
.cube__face--front {
transform: rotateY(0deg);
}
.cube__face--right {
transform: rotateY(90deg);
}
.cube__face--back {
transform: rotateY(180deg);
}
.cube__face--left {
transform: rotateY(-90deg);
}
.cube__face--top {
transform: rotateX(90deg);
}
.cube__face--bottom {
transform: rotateX(-90deg);
}
複製代碼
結果以下圖
貌似看不出怎麼變換的?咱們將以上重合的兩個面的旋轉角度稍微調整下,就能夠更直觀地看出效果:
各面繼續向兩側位移
.cube__face--front {
transform: rotateY(0deg) translateZ(100px);
}
.cube__face--right {
transform: rotateY(90deg) translateZ(100px);
}
.cube__face--back {
transform: rotateY(180deg) translateZ(100px);
}
.cube__face--left {
transform: rotateY(-90deg) translateZ(100px);
}
.cube__face--top {
transform: rotateX(90deg) translateZ(100px);
}
.cube__face--bottom {
transform: rotateX(-90deg) translateZ(100px);
}
複製代碼
整個過程可用下圖表示:
最終效果:
經過調整容器樣式的 perspective 屬性值,將視角放置在立方體中。將每一個面的尺寸放大,添加上全景圖切出的 6 面圖,添加上鼠標事件,即可實現 360 全景效果。
掃碼看效果:
Three.js 是一款運行在瀏覽器中的 webGL 引擎,咱們能夠用它建立各類三維場景,包括了攝影機、光影、材質等各類對象,利用 Three.js 咱們能夠輕鬆地實現各類想要的幾何體。 上面的方案中,咱們用 CSS3 「拼」出了一個立方體,而 webGL 中立方體等各類幾何體是最多見的。咱們能夠利用 Threejs 中的立方體實現全景圖功能,把立方體當整天空盒子,將無縫銜接的圖片貼上,看起來就像在一個場景中,相機通常放置在中央,只要離邊緣足夠遠就看不出是立方體,但若是超出邊界就能看到他們。 下面是簡單的實現過程:
初始化一個立方體幾何,給材料上色,便獲得了一個立方體:
const geometry = new THREE.BoxBufferGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x156289 });
const skyBox = new THREE.Mesh(geometry, material);
複製代碼
將立方體的紋理顏色換成圖片紋理:
const texture = new THREE.TextureLoader().load('textures/crate.gif');
const geometry = new THREE.BoxBufferGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ map: texture });
const skyBox = new THREE.Mesh(geometry, material);
複製代碼
將 6 張全景圖片替換掉上面相同的紋理:
圖片加載的順序是正 X(px.jpg),負 X(nx.jpg),正 Y(py.jpg),負 Y(ny.jpg),正 Z(pz.jpg)和負 Z(nz.jpg),將他們分別賦給 6 個材質的貼圖,做爲立方體 skyBox 的材質。
const geometry = new THREE.BoxBufferGeometry(1, 1, 1);
const textures = [
'https://img.alicdn.com/imgextra/i3/O1CN01LsO1Bk20QbKpFTUQr_!!6000000006844-0-tps-1500-1500.jpg',
'https://img.alicdn.com/imgextra/i3/O1CN01uTWCLc1XOCOuA92H0_!!6000000002913-0-tps-1500-1500.jpg',
'https://img.alicdn.com/imgextra/i4/O1CN016lU3YJ1JdrJuFTcWt_!!6000000001052-0-tps-1500-1500.jpg',
'https://img.alicdn.com/imgextra/i2/O1CN01nYe2Mn1ohkmBVyKpp_!!6000000005257-0-tps-1500-1500.jpg',
'https://img.alicdn.com/imgextra/i4/O1CN014TNffn1nlaTfA98Fg_!!6000000005130-0-tps-1500-1500.jpg',
'https://img.alicdn.com/imgextra/i1/O1CN01sS5m781ya6JgLSaVk_!!6000000006594-0-tps-1500-1500.jpg',
];
const materials = [];
const textureLoader = new THREE.TextureLoader();
for (let i = 0; i < 6; i++) {
materials.push(
new THREE.MeshBasicMaterial({ map: textureLoader.load(textures[i]) })
);
}
const skyBox = new THREE.Mesh(geometry, materials);
複製代碼
由於相機在 skyBox 的內部,而內部的面不會顯示,因此要將 X 軸或者 Z 軸的放大倍數變爲負數,這樣才能看到內部,scale.z=-1 時至關於將 Z 軸正向的面移到 Z 軸負方向上。
skyBox.geometry.scale(1, 1, -1);
複製代碼
進一步優化體驗:
camera.position.z = 0.01; // 將相機放在裏面
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableZoom = false; // 禁用放大
controls.enablePan = false; // 禁用雙指縮放
controls.enableDamping = true; // 開啓阻尼效果
controls.rotateSpeed = -0.25; // 旋轉方向取反,使內部拖拽旋轉方向一致
複製代碼
掃碼看效果:
pannellum 是一個輕量級、免費、開源的基於 webGL 的全景播放器。支持多種投影方式、支持熱點、支持漫遊、視頻等,且文檔清晰明瞭、簡單易用、API 比較豐富,易於功能擴展。 使用:
pannellum.viewer('container', {
type: 'cubemap',
cubeMap: [
'https://img.alicdn.com/imgextra/i4/O1CN014TNffn1nlaTfA98Fg_!!6000000005130-0-tps-1500-1500.jpg',
'https://img.alicdn.com/imgextra/i3/O1CN01LsO1Bk20QbKpFTUQr_!!6000000006844-0-tps-1500-1500.jpg',
'https://img.alicdn.com/imgextra/i1/O1CN01sS5m781ya6JgLSaVk_!!6000000006594-0-tps-1500-1500.jpg',
'https://img.alicdn.com/imgextra/i3/O1CN01uTWCLc1XOCOuA92H0_!!6000000002913-0-tps-1500-1500.jpg',
'https://img.alicdn.com/imgextra/i4/O1CN016lU3YJ1JdrJuFTcWt_!!6000000001052-0-tps-1500-1500.jpg',
'https://img.alicdn.com/imgextra/i2/O1CN01nYe2Mn1ohkmBVyKpp_!!6000000005257-0-tps-1500-1500.jpg',
],
});
複製代碼
掃碼看效果:
基於 pannellum 咱們封裝了一個 360 全景容器組件 cube,支持了 pannellum 的全部配置,支持了立方體投影和球型投影兩種方式、場景切換、陀螺儀效果等。目前該組件已應用在飛豬酒店詳情中。 使用:
export default function CubeDemo() {
const [scenes, setScenes] = useState([]);
useEffect(() => {
// 模擬異步請求
setTimeout(() => {
setScenes(
[
{
preview: 'https://img.alicdn.com/imgextra/i1/O1CN01dVOIEe1IhEcaIPw2z_!!6000000000924-0-tps-100-100.jpg',
title: '客廳',
// 或 panorama: 'https://img.alicdn.com/imgextra/i4/6000000007276/O1CN01Hp5gIf23cSOvzzA9k_!!6000000007276-0-hotel.jpg', //type: 'equirectangular' 時須要
cubeMap: [
// 順序是:前、右、後、左、上、下
'https://gw.alicdn.com/imgextra/i3/O1CN01550SRA1JcwWgs0sIj_!!6000000001050-0-tps-1500-1500.jpg',
'https://img.alicdn.com/imgextra/i4/O1CN01e796bV1P2CRfCQkrA_!!6000000001782-0-tps-1500-1500.jpg',
'https://img.alicdn.com/imgextra/i4/O1CN01GcW84X29SHK4oJlWc_!!6000000008066-0-tps-1500-1500.jpg',
'https://img.alicdn.com/imgextra/i2/O1CN01ZHLck11GX2ZgBHA4o_!!6000000000631-0-tps-1500-1500.jpg',
'https://img.alicdn.com/imgextra/i2/O1CN019c9xKu1ig1aC7pWPk_!!6000000004441-0-tps-1500-1500.jpg',
'https://img.alicdn.com/imgextra/i4/O1CN01XfaKOu1kzNYODz7HD_!!6000000004754-0-tps-1500-1500.jpg'
]
},
{
preview: 'https://img.alicdn.com/imgextra/i1/O1CN01KU3hrj1uJNO2OdyaC_!!6000000006016-0-tps-100-100.jpg',
// 或 panorama: 'https://img.alicdn.com/imgextra/i4/6000000004110/O1CN01lKLSsP1gEQSNAMIsJ_!!6000000004110-0-hotel.jpg',
title: '書房',
cubeMap: [
'https://img.alicdn.com/imgextra/i1/O1CN01fWDIfB1bWgC3NnVVa_!!6000000003473-0-tps-1500-1500.jpg',
'https://img.alicdn.com/imgextra/i2/O1CN01xt97cb1YMeg4BOCbI_!!6000000003045-0-tps-1500-1500.jpg',
'https://img.alicdn.com/imgextra/i1/O1CN01xKTq1u1DR8cdeMeYt_!!6000000000212-0-tps-1500-1500.jpg',
'https://img.alicdn.com/imgextra/i3/O1CN01Zko8Qy1p1uCLUYBji_!!6000000005301-0-tps-1500-1500.jpg',
'https://img.alicdn.com/imgextra/i3/O1CN01k3AVvK28W71UNWXW7_!!6000000007939-0-tps-1500-1500.jpg',
'https://img.alicdn.com/imgextra/i1/O1CN015MBT6P1N8x3J83Fqo_!!6000000001526-0-tps-1500-1500.jpg'
]
}
......
]
);
}, 1000);
}, []);
return (
<Cube container="viwer" config={{ type: 'cubemap', // or 'equirectangular' scenes, }} />
);
}
複製代碼
掃碼看效果:
以上的實現所有是利用立方體實現的,除此以外,咱們還可使用圓柱體投影的方式,three.js 和 pannellum 都支持這種方式。 three.js 中:
const geometry = new THREE.SphereBufferGeometry(300, 60, 40); // 初始化一個球體
geometry.scale(-1, 1, 1); // 翻轉x軸,將全部面朝向裏
const texture = new THREE.TextureLoader().load(
'https://img.alicdn.com/imgextra/i2/6000000004217/O1CN01djW9bE1h1QprTMP5d_!!6000000004217-0-hotel.jpg'
); // 加載全景紋理
const material = new THREE.MeshBasicMaterial({ map: texture });
mesh = new THREE.Mesh(geometry, material);
複製代碼
pannellum 中:
pannellum.viewer('panorama', {
type: 'equirectangular',
panorama:
'https://img.alicdn.com/imgextra/i2/6000000004217/O1CN01djW9bE1h1QprTMP5d_!!6000000004217-0-hotel.jpg',
});
複製代碼
頁面和代碼可參考 webgl_panorama_equirectangular 球型全景和立方體全景兩種方式比較起來各有本身的特色,好比:
飛豬酒店詳情中,服務端對這兩種方式都提供了圖片資源,但在使用球型全景的方式時少數酒店上傳的圖片過大,致使部分手機上組件加載資源出錯,因此最終決定採用立方體的方式()。
krpano 是一款較專業的全景引擎平臺。目前市場中較大的全景平臺大部分都用了 krpano 。但因爲它用法較複雜,有必定的學習成本,須要掌握其使用的 XML 的各類配置、功能較多較重且是收費的,對於酒店詳情的簡單的業務場景來講有點重了,因此權衡以後沒有選擇它,而是選擇了相對輕量的 pannellum。 使用:
<div id='container'></div>
複製代碼
embedpano({
xml:
'https://raw.githubusercontent.com/xiaotianxia/three.js-learning/gh-pages/examples/config.xml',
target: 'container',
html5: 'auto',
mobilescale: 1.0,
});
複製代碼
掃碼看效果:
本文敘述了實現 360 全景功能 4 種不一樣的方案,包括:CSS三、Three.js、pannellum 和 krpano,在基於 webGL 的方案中,介紹了兩種主要的投影方式:立方體投影和球型投影,並給出了 demo 代碼和頁面,推薦了基於 pannellum 開發的 360 全景容器組件, 若有錯誤,歡迎指正。