React + Threejs + Swiper 实现全景图效果的完整代码


Posted in Javascript onJune 28, 2021

  咱先看看全景图实现效果:展示地址
  截图:

React + Threejs + Swiper 实现全景图效果的完整代码

  体验了一下是不是感觉周围环境转了一圈,感觉世界是圆的??
  没错!恭喜你答对了!地球就是圆的!?

全景效果实现

  有了上面的提示,对 threejs 有一点了解的小伙伴可能就猜出来了,这个全景效果其实就是使用一个球体实现的~ 而我们只是在球体表面上贴了一张纹理贴图而已(滚轮向外滚就可以看到这个球体了,看上去像个玻璃球,怪好看的,还有个彩蛋?(好吧,说出来就不是彩蛋了)):

React + Threejs + Swiper 实现全景图效果的完整代码

  初始时,我们的视角在球体正中心,视角的移动则是依靠 threejs 提供的工具 OrbitControls 来控制。

  那么创建这个球体的代码如下:

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 文档

轮播图

  轮播图实现则是使用 swiper 这个库,使用起来非常方便,具体可自行查阅文档。
  在滑动轮播图时,会触发一个 onSliderChange 事件,这个事件传入当前的 swiper 作为参数,我们就可以通过当前激活的元素来获取图片并替换球体的纹理贴图了:

onSliderChange = curSwiper => {
    const mesh = this.mesh;
    const texture = imglist[curSwiper.activeIndex].default;
    mesh.material.map = new THREE.TextureLoader().load(texture);
};

  下面是我的 swiper 设置,其中 SwiperSlider 是一个可滑动的轮播图卡片,EffectCoverflow 是滑动时触发的效果,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}	// 轮播图里可预览图片数
    onSlideChange={this.onSliderChange}	// 滑动时触发的回调
    onSwiper={(swiper) => console.log(swiper)}	// 初始加载时触发的回调
    direction='vertical'	// 轮播图方向,默认是水平 horizontal
    effect={'coverflow'}	// 滑动效果
    grabCursor={true}	// 鼠标放在轮播图上是否显示拖拽
    centeredSlides={true}	// 当前处于激活状态的图片是否要居中
    coverflowEffect={{	// coverflow 效果参数设置,可自行调整
        "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>

  全景效果的实现就说到这了,当然,如果什么地方有疑问可以留言或者参考我的代码(下面贴出来),只要对 threejs 和 react 有一定了解的同学我相信实现这么一个效果并不难,代码量也很小~

完整代码

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;

        // 设置提示框的全局配置
        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('?恭喜你发现了全景效果的小秘密~?');
                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>
        );
    }
}

到此这篇关于React + Threejs + Swiper 实现全景图效果的完整代码的文章就介绍到这了,更多相关React全景图内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
javascript基本语法分析说明
Jun 15 Javascript
jQuery 扩展对input的一些操作方法
Oct 30 Javascript
JS函数验证总结(方便js客户端输入验证)
Oct 29 Javascript
JS批量操作CSS属性详细解析
Dec 16 Javascript
javascript判断是否按回车键并解决浏览器之间的差异
May 13 Javascript
jquery实现倒计时代码分享
Jun 13 Javascript
jQuery禁用键盘后退屏蔽F5刷新及禁用右键单击
Jan 22 Javascript
JavaScript是如何实现继承的(六种方式)
Mar 31 Javascript
基于jQuery实现无缝轮播与左右点击效果
May 13 jQuery
开源一个微信小程序仪表盘组件过程解析
Jul 30 Javascript
详解vue v-model
Aug 31 Javascript
如何在vue中使用kindeditor富文本编辑器
Dec 19 Vue.js
Vue实现tab导航栏并支持左右滑动功能
React列表栏及购物车组件使用详解
React如何创建组件
Jun 27 #Javascript
Vue3.0写自定义指令的简单步骤记录
关于JavaScript回调函数的深入理解
Jun 27 #Javascript
vue.js Router中嵌套路由的实用示例
Jun 27 #Vue.js
vite+vue3.0+ts+element-plus快速搭建项目的实现
You might like
php 数组的指针操作实现代码
2011/02/08 PHP
PHP 线程安全与非线程安全版本的区别深入解析
2013/08/06 PHP
详谈PHP文件目录基础操作
2014/11/11 PHP
Phpstorm+Xdebug断点调试PHP的方法
2018/05/14 PHP
中国地区三级联动下拉菜单效果分析
2012/11/15 Javascript
JavaScript 处理Iframe自适应高度(同或不同域名下)
2013/03/29 Javascript
Google Dart编程语法和基本类型学习教程
2013/11/27 Javascript
JS替换字符串中字符即替换全部而不是第一个
2014/06/04 Javascript
js怎么覆盖原有方法实现重写
2014/09/04 Javascript
如何解决ligerUI布局时Center中的Tab高度大小
2015/11/24 Javascript
解决jQuery上传插件Uploadify出现Http Error 302错误的方法
2015/12/18 Javascript
jquery实现具有收缩功能的垂直导航菜单
2016/02/16 Javascript
微信小程序 scroll-view组件实现列表页实例代码
2016/12/14 Javascript
如何清除IE10+ input X 文本框的叉叉和密码输入框的眼睛图标
2016/12/21 Javascript
Ajax 加载数据 练习代码
2017/01/05 Javascript
自定义vue全局组件use使用、vuex的使用详解
2017/06/14 Javascript
Vue和Bootstrap的整合思路详解
2017/06/30 Javascript
VUE v-for循环中每个item节点动态绑定不同函数的实例
2018/09/26 Javascript
微信小程序使用component自定义toast弹窗效果
2018/11/27 Javascript
webpack安装配置与常见使用过程详解(结合vue)
2020/06/01 Javascript
[01:04:20]完美世界DOTA2联赛PWL S2 LBZS vs Forest 第一场 11.29
2020/12/02 DOTA
python修改操作系统时间的方法
2015/05/18 Python
使用Python的Twisted框架构建非阻塞下载程序的实例教程
2016/05/25 Python
python版微信跳一跳游戏辅助
2018/01/11 Python
pandas读取csv文件,分隔符参数sep的实例
2018/12/12 Python
Python 实现域名解析为ip的方法
2019/02/14 Python
python爬虫租房信息在地图上显示的方法
2019/05/13 Python
Python Threading 线程/互斥锁/死锁/GIL锁
2019/07/21 Python
使用openCV去除文字中乱入的线条实例
2020/06/02 Python
matplotlib更改窗口图标的方法示例
2021/02/03 Python
HTML5 canvas基本绘图之填充样式实现
2016/06/27 HTML / CSS
Yankee Candle官网:美国最畅销蜡烛品牌之一
2020/01/05 全球购物
研发工程师岗位职责
2014/04/28 职场文书
农林经济管理专业自荐信
2014/09/01 职场文书
交通事故委托书范本
2014/09/28 职场文书
2015新年寄语大全
2014/12/08 职场文书