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 相关文章推荐
解决angular的$http.post()提交数据时后台接收不到参数值问题的方法
Dec 10 Javascript
JavaScript浏览器对象模型BOM(BrowserObjectModel)实例详解
Nov 29 Javascript
概述BootStrap中role=&quot;form&quot;及role作用角色
Dec 08 Javascript
JS无缝滚动效果实现方法分析
Dec 21 Javascript
js实现省份下拉菜单效果
Feb 15 Javascript
js图片轮播插件的封装
Jul 21 Javascript
vue实现仿淘宝结账页面实例代码
Nov 08 Javascript
使用form-create动态生成vue自定义组件和嵌套表单组件
Jan 18 Javascript
Node.js Event Loop各阶段讲解
Mar 08 Javascript
JS实现图片懒加载(lazyload)过程详解
Apr 02 Javascript
Vue项目接入Paypal实现示例详解
Jun 04 Javascript
JavaScript实现瀑布流布局的3种方式
Dec 27 Javascript
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提取中英文词语以及数字的首字母的方法介绍
2013/04/23 PHP
PHP基本语法总结
2014/09/06 PHP
jquery+php+ajax显示上传进度的多图片上传并生成缩略图代码
2014/10/15 PHP
Laravel 5 框架入门(二)构建 Pages 的管理功能
2015/04/09 PHP
基于php实现的php代码加密解密类完整实例
2016/10/12 PHP
php中array_slice和array_splice函数解析
2016/10/18 PHP
PHP巧妙利用位运算实现网站权限管理的方法
2017/03/12 PHP
jquery animate图片模向滑动示例代码
2011/01/26 Javascript
面向对象继承实例(a如何继承b问题)(自写)
2013/07/01 Javascript
5个数组Array方法: indexOf、filter、forEach、map、reduce使用实例
2015/01/29 Javascript
Bootstrap3制作图片轮播效果
2016/05/12 Javascript
最简单的tab切换实例代码
2016/05/13 Javascript
全面解析Bootstrap中nav、collapse的使用方法
2016/05/22 Javascript
JavaScript奇技淫巧44招【实用】
2016/12/11 Javascript
bootstrap选项卡使用方法解析
2017/01/11 Javascript
JS简单实现数组去重的方法示例
2017/03/27 Javascript
详解 vue better-scroll滚动插件排坑
2018/02/08 Javascript
Element ui 下拉多选时新增一个选择所有的选项
2019/08/21 Javascript
JS多个异步请求 按顺序执行next实现解析
2019/09/16 Javascript
微信小程序实现分享商品海报功能
2019/09/30 Javascript
构建大型 Vue.js 项目的10条建议(小结)
2019/11/14 Javascript
webgl实现物体描边效果的方法介绍
2019/11/27 Javascript
Python实现的端口扫描功能示例
2018/04/08 Python
django1.11.1 models 数据库同步方法
2018/05/30 Python
Windows下python3.7安装教程
2018/07/31 Python
如何利用Python 进行边缘检测
2020/10/14 Python
Python self用法详解
2020/11/28 Python
html5适合移动应用开发的12大特性
2014/03/19 HTML / CSS
工商管理毕业生推荐信
2013/12/24 职场文书
幼儿园家长安全责任书
2014/07/22 职场文书
批评与自我批评范文
2014/10/15 职场文书
答谢词范文
2015/01/05 职场文书
单位接收函格式
2015/01/30 职场文书
项目技术负责人岗位职责
2015/04/13 职场文书
Java面试题冲刺第十九天--数据库(4)
2021/08/07 Java/Android
MySQL磁盘碎片整理实例演示
2022/04/03 MySQL