React + Threejs + Swiper 實(shí)現(xiàn)全景圖效果的完整代碼
咱先看看全景圖實(shí)現(xiàn)效果:展示地址
截圖:

體驗(yàn)了一下是不是感覺(jué)周?chē)h(huán)境轉(zhuǎn)了一圈,感覺(jué)世界是圓的?😁
沒(méi)錯(cuò)!恭喜你答對(duì)了!地球就是圓的!👀
全景效果實(shí)現(xiàn)
有了上面的提示,對(duì) threejs 有一點(diǎn)了解的小伙伴可能就猜出來(lái)了,這個(gè)全景效果其實(shí)就是使用一個(gè)球體實(shí)現(xiàn)的~ 而我們只是在球體內(nèi)表面上貼了一張紋理貼圖而已(滾輪向外滾就可以看到這個(gè)球體了,看上去像個(gè)玻璃球,怪好看的,還有個(gè)彩蛋😁(好吧,說(shuō)出來(lái)就不是彩蛋了)):

初始時(shí),我們的視角在球體正中心,視角的移動(dòng)則是依靠 threejs 提供的工具 OrbitControls 來(lái)控制。
那么創(chuàng)建這個(gè)球體的代碼如下:
const geometry = new THREE.SphereBufferGeometry(500, 32, 32);
geometry.scale(-1, 1, 1); // 將紋理反貼
const material = new THREE.MeshBasicMaterial({
map: new THREE.TextureLoader().load(imglist[0].default) // 傳入圖片的URL或者路徑,也可以是 Data URI.
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enablePan = false;
controls.maxDistance = 1000;
不知道 Data URI 是什么的可以看看 MDN 文檔
輪播圖
輪播圖實(shí)現(xiàn)則是使用 swiper 這個(gè)庫(kù),使用起來(lái)非常方便,具體可自行查閱文檔。
在滑動(dòng)輪播圖時(shí),會(huì)觸發(fā)一個(gè) onSliderChange 事件,這個(gè)事件傳入當(dāng)前的 swiper 作為參數(shù),我們就可以通過(guò)當(dāng)前激活的元素來(lái)獲取圖片并替換球體的紋理貼圖了:
onSliderChange = curSwiper => {
const mesh = this.mesh;
const texture = imglist[curSwiper.activeIndex].default;
mesh.material.map = new THREE.TextureLoader().load(texture);
};
下面是我的 swiper 設(shè)置,其中 SwiperSlider 是一個(gè)可滑動(dòng)的輪播圖卡片,EffectCoverflow 是滑動(dòng)時(shí)觸發(fā)的效果,swiper 中提供了四種可選效果:Fade、Coverflow、Flip 以及 Cube。imglist 則是一組圖片,其中 imglist[i].default 屬性保存了圖片的 base64 編碼。
import { Swiper, SwiperSlide } from 'swiper/react';
import SwiperCore, { EffectCoverflow } from 'swiper';
import 'swiper/swiper.min.css';
import 'swiper/components/effect-coverflow/effect-coverflow.min.css';
SwiperCore.use([EffectCoverflow]);
//....
<Swiper
className='panoramic-imgs'
spaceBetween={50} // 間距
slidesPerView={3} // 輪播圖里可預(yù)覽圖片數(shù)
onSlideChange={this.onSliderChange} // 滑動(dòng)時(shí)觸發(fā)的回調(diào)
onSwiper={(swiper) => console.log(swiper)} // 初始加載時(shí)觸發(fā)的回調(diào)
direction='vertical' // 輪播圖方向,默認(rèn)是水平 horizontal
effect={'coverflow'} // 滑動(dòng)效果
grabCursor={true} // 鼠標(biāo)放在輪播圖上是否顯示拖拽
centeredSlides={true} // 當(dāng)前處于激活狀態(tài)的圖片是否要居中
coverflowEffect={{ // coverflow 效果參數(shù)設(shè)置,可自行調(diào)整
"rotate": 50,
"stretch": 0,
"depth": 100,
"modifier": 1,
"slideShadows": true
}}
{
imglist.map((img, idx) => {
return <SwiperSlide key={idx}>
<img src={img.default} className='panoramic-img'></img>
</SwiperSlide>
})
}
</Swiper>
全景效果的實(shí)現(xiàn)就說(shuō)到這了,當(dāng)然,如果什么地方有疑問(wèn)可以留言或者參考我的代碼(下面貼出來(lái)),只要對(duì) threejs 和 react 有一定了解的同學(xué)我相信實(shí)現(xiàn)這么一個(gè)效果并不難,代碼量也很小~
完整代碼
import React, { Component } from 'react';
import Layout from '@theme/Layout';
import Head from '@docusaurus/Head';
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import * as _ from 'underscore';
import { message } from 'antd';
import { Swiper, SwiperSlide } from 'swiper/react';
import SwiperCore, { EffectCoverflow } from 'swiper';
import 'swiper/swiper.min.css';
import 'swiper/components/effect-coverflow/effect-coverflow.min.css';
import './index.css';
import imgs from './imgs.json';
SwiperCore.use([EffectCoverflow]);
const imglist = imgs.map(img => {
return require('../../../static/img/panoramic/' + img.name);
});
export default class Panormatic extends Component {
constructor() {
super();
this.renderer = null;
this.camera = null;
this.scene = null;
this.container = null;
this.controls = null;
this.showMessage = true; // 彩蛋提示
}
componentDidMount() {
const container = document.getElementById('panoramic-canvas-container');
const canvas = document.getElementById('panoramic-canvas');
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
renderer.setClearColor(0xffffff); // b2e0df 綠豆沙色
renderer.setPixelRatio( window.devicePixelRatio );
const height = container.clientHeight;
const width = container.clientWidth;
renderer.setSize(width, height);
const camera = new THREE.PerspectiveCamera(60, width / height, 1, 30000);
camera.position.set(0, 0, 1);
camera.center = new THREE.Vector3(0, 0, 0);
const scene = new THREE.Scene();
const geometry = new THREE.SphereBufferGeometry(500, 32, 32);
geometry.scale(-1, 1, 1); // 將紋理反貼
const material = new THREE.MeshBasicMaterial({
map: new THREE.TextureLoader().load(imglist[0].default)
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
const controls = new OrbitControls(camera, renderer.domElement);
// controls.enableZoom = false;
controls.enablePan = false;
controls.maxDistance = 1000;
this.renderer = renderer;
this.camera = camera;
this.scene = scene;
this.container = container;
this.controls = controls;
this.mesh = mesh;
// 設(shè)置提示框的全局配置
message.config({
top: 100,
duration: 3.5,
maxCount: 1,
});
this.onControlsChange = _.throttle(this.onChange, 100);
controls.addEventListener('change', this.onControlsChange);
window.addEventListener('resize', this.onWindowResize);
this.renderLoop();
}
componentWillUnmount() {
const mesh = this.mesh;
mesh.material.dispose();
mesh.geometry.dispose();
this.scene.remove(mesh);
window.removeEventListener('resize', this.onWindowResize);
this.controls.removeEventListener('change', this.onControlsChange);
message.destroy();
}
onChange = (e) => {
const camera = this.camera;
if (camera.position.distanceTo(camera.center) >= 700) {
if (this.showMessage) {
message.success('🎊恭喜你發(fā)現(xiàn)了全景效果的小秘密~🎉');
this.showMessage = false;
}
} else {
this.showMessage = true;
}
}
onSliderChange = (curSwiper) => {
const mesh = this.mesh;
const texture = imglist[curSwiper.activeIndex].default;
mesh.material.map = new THREE.TextureLoader().load(texture);
};
onWindowResize = () => {
const camera = this.camera;
const renderer = this.renderer;
const width = this.container.clientWidth;
const height = this.container.clientHeight;
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize(width, height);
};
renderLoop = () => {
this.renderer.render(this.scene, this.camera);
requestAnimationFrame(this.renderLoop);
};
render() {
return (
<Layout>
<Head>
<title>全景圖 | Yle</title>
</Head>
<div id='panoramic-container'>
<Swiper
className='panoramic-imgs'
spaceBetween={50}
slidesPerView={3}
onSlideChange={this.onSliderChange}
onSwiper={(swiper) => console.log(swiper)}
direction='vertical'
effect={'coverflow'}
grabCursor={true}
centeredSlides={true}
coverflowEffect={{
"rotate": 50,
"stretch": 0,
"depth": 100,
"modifier": 1,
"slideShadows": true
}}
>
{
imglist.map((img, idx) => {
return <SwiperSlide key={idx}>
<img src={img.default} className='panoramic-img'></img>
</SwiperSlide>
})
}
</Swiper>
<div id='panoramic-canvas-container'>
<canvas id='panoramic-canvas'></canvas>
</div>
</div>
</Layout>
);
}
}
到此這篇關(guān)于React + Threejs + Swiper 實(shí)現(xiàn)全景圖效果的完整代碼的文章就介紹到這了,更多相關(guān)React全景圖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React??memo允許你的組件在?props?沒(méi)有改變的情況下跳過(guò)重新渲染的問(wèn)題記錄
使用?memo?將組件包裝起來(lái),以獲得該組件的一個(gè)?記憶化?版本,只要該組件的?props?沒(méi)有改變,這個(gè)記憶化版本就不會(huì)在其父組件重新渲染時(shí)重新渲染,這篇文章主要介紹了React??memo允許你的組件在?props?沒(méi)有改變的情況下跳過(guò)重新渲染,需要的朋友可以參考下2024-06-06
React18的useEffect執(zhí)行兩次如何應(yīng)對(duì)
這篇文章主要給大家介紹了關(guān)于React18的useEffect執(zhí)行兩次如何應(yīng)對(duì)的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用React具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07
React Hooks: useEffect()調(diào)用了兩次問(wèn)題分析
這篇文章主要為大家介紹了React Hooks: useEffect()調(diào)用了兩次問(wèn)題分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11
npx create-react-app xxx創(chuàng)建項(xiàng)目報(bào)錯(cuò)的解決辦法
這篇文章主要介紹了npx create-react-app xxx創(chuàng)建項(xiàng)目報(bào)錯(cuò)的解決辦法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02
react實(shí)現(xiàn)記錄拖動(dòng)排序
這篇文章主要介紹了react實(shí)現(xiàn)記錄拖動(dòng)排序的相關(guān)資料,需要的朋友可以參考下2023-07-07
React修改數(shù)組對(duì)象的注意事項(xiàng)及說(shuō)明
這篇文章主要介紹了React修改數(shù)組對(duì)象的注意事項(xiàng)及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12

