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中令你抓狂的魔术变量
Nov 30 Javascript
js innerHTML 的一些问题的解决方法
Jun 22 Javascript
TextArea不支持maxlength的解决办法(jquery)
Sep 13 Javascript
Textbox控件注册回车事件及触发按钮提交事件具体实现
Mar 04 Javascript
jQuery时间插件jquery.clock.js用法实例(5个示例)
Jan 14 Javascript
Bootstrap的fileinput插件实现多文件上传的方法
Sep 05 Javascript
微信小程序 解决请求服务器手机预览请求不到数据的方法
Jan 04 Javascript
js仿淘宝商品放大预览功能
Mar 15 Javascript
webpack实用小功能介绍
Jan 02 Javascript
AngularJs返回前一页面时刷新一次前面页面的方法
Oct 09 Javascript
Swiper.js实现移动端元素左右滑动
Sep 08 Javascript
关于ligerui子页面关闭后,父页面刷新,重新加载的方法
Sep 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
利用ThinkPHP内置的ThinkAjax实现异步传输技术的实现方法
2011/12/19 PHP
PHP中集成PayPal标准支付的实现方法分享
2012/02/06 PHP
php中ob_flush函数和flush函数用法分析
2015/03/18 PHP
详解php设置session(过期、失效、有效期)
2015/11/12 PHP
php微信开发之关注事件
2018/06/14 PHP
PHP开发的文字水印,缩略图,图片水印实现类与用法示例
2019/04/12 PHP
浅析PHP7 的垃圾回收机制
2019/09/06 PHP
laravel实现Auth认证,登录、注册后的页面回跳方法
2019/09/30 PHP
用javascript实现无刷新更新数据的详细步骤 asp
2006/12/26 Javascript
javascript showModalDialog,open取得父窗口的方法
2010/03/10 Javascript
jquery的Tooltip插件 qtip使用详细说明
2010/09/08 Javascript
dojo学习第二天 ajax异步请求之绑定列表
2011/08/29 Javascript
腾讯UED 漂亮的提示信息效果代码
2011/09/12 Javascript
浅谈EasyUI中Treegrid节点的删除
2015/03/01 Javascript
JavaScript中用toString()方法返回时间为字符串
2015/06/12 Javascript
javascript下使用Promise封装FileReader
2016/02/19 Javascript
再谈Angular4 脏值检测(性能优化)
2018/04/23 Javascript
Vuejs+vue-router打包+Nginx配置的实例
2018/09/20 Javascript
vue spa应用中的路由缓存问题与解决方案
2019/05/31 Javascript
Layui数据表格跳转到指定页的实现方法
2019/09/05 Javascript
vue 在methods中调用mounted的实现操作
2020/08/07 Javascript
Python中Collection的使用小技巧
2014/08/18 Python
Python基于matplotlib实现绘制三维图形功能示例
2018/01/18 Python
python 连接各类主流数据库的实例代码
2018/01/30 Python
Python中将两个或多个list合成一个list的方法小结
2019/05/12 Python
keras-siamese用自己的数据集实现详解
2020/06/10 Python
Python 实现3种回归模型(Linear Regression,Lasso,Ridge)的示例
2020/10/15 Python
一款基于css3的列表toggle特效实例教程
2015/01/04 HTML / CSS
西班牙多品牌鞋店连锁店:Krack
2018/11/30 全球购物
西班牙宠物用品和食品网上商店:Tiendanimal
2019/06/06 全球购物
说出一些常用的类,包,接口
2014/09/22 面试题
Java提供了哪些企业应用编程接口
2015/02/13 面试题
叙述DBMS对数据控制功能有哪些
2016/06/12 面试题
中英文求职信范文
2014/01/27 职场文书
村党支部书记承诺书
2014/05/29 职场文书
力学专业求职信
2014/07/23 职场文书