介绍
@nicepkg/vr360-core
是一个基于 threejs 的全景库,非常适合用来做全景看房、全景街景、全景景点等业务需求。
它支持 json 配置驱动视图,你可以用 json 配置的方式快速实现常见全景需求。
特性
- json 驱动视图,快速实现全景业务需求
- 高性能,支持自动找出 json 变更的部分,最小化更新到 3d 全景里,不会整个场景重建。
- 支持增删改提示点,hover 提示点的弹窗支持自定义定制,支持自定义提示点的贴图。
- 内置场景切换穿梭动画
- 暴露 scene、camera、renderer 等 threejs 对象,方便你更高的定制化
- 基于事件驱动的数据交互,设计框架无关性
- 完整的 ts 支持
安装
npm i @nicepkg/vr360-core threejs
yarn add @nicepkg/vr360-core threejs
pnpm add @nicepkg/vr360-core threejs
浏览器(CDN)
<!-- 引入 threejs -->
<script src="https://unpkg.com/three@0.145.0/build/three.min.js"></script>
<!-- 引入 vr360-core -->
<script src="https://unpkg.com/@nicepkg/vr360-core@0.3.1"></script>
<script>
// 使用
const {Vr360} = Vr360Core // 原生使用时,所有的导出都在 window.Vr360Core 里
// 初始化全景实例
const vr360 = new Vr360({...})
// 开始渲染全景
vr360.render()
</script>
<!-- 引入 threejs -->
<script src="https://cdn.jsdelivr.net/npm/three@0.145.0/build/three.min.js"></script>
<!-- 引入 vr360-core -->
<script src="https://cdn.jsdelivr.net/npm/@nicepkg/vr360-core@0.3.1"></script>
<script>
// 使用
const {Vr360} = Vr360Core // 原生使用时,所有的导出都在 window.Vr360Core 里
// 初始化全景实例
const vr360 = new Vr360({...})
// 开始渲染全景
vr360.render()
</script>
为什么
如果产品叫你实现一个类似全景看房的功能,而且时间紧,你会怎么做?
学 threejs 知识,然后徒手撸出全景,然后自己抽象出一个 json 配置驱动和后端数据联调对接?
可能还要自己写一个全景编辑器?
可以但没必要,你完全可以把时间省下来做些有意义的事,你只要用 @nicepkg/vr360-core
就可以了。
简单点的业务需求仅需 json 配置就能实现,重要是这种代码拉条哈士奇也能维护。
编辑器还在开发中,复杂度不会消失,只会转移,当你岁月静好,一定是作者在为你负重前行。
使用
请阅读我们的 属性文档、 函数文档、事件文档,了解如何使用 @nicepkg/vr360-core
。
除了下面的简易示例,你也可以浏览我们的 完整项目示例
简易示例效果
简易示例代码(cv 专用)
<template>
<div class="demo">
<!-- 提示 -->
<div
ref="tipRef"
class="demo-tip"
:style="{
transform: `translate(${tip.left}px, ${tip.top + 50}px)`,
zIndex: tip.show ? 99 : -1,
visibility: tip.show ? 'visible' : 'hidden'
}"
>
<!-- 提示标题 -->
<div class="demo-tip-title">{{ tip.title }}</div>
<!-- 提示内容 -->
<div class="demo-tip-content">{{ tip.content }}</div>
</div>
<!-- 360全景容器 -->
<div ref="containerRef" class="demo-container"></div>
<!-- 底部切换场景 -->
<div class="demo-bottom-bar">
<div
v-for="space in spacesConfig"
:key="space.id"
class="demo-bottom-card"
@click="handleSwitchSpace(space)"
:style="{
backgroundImage: `url(${space.cubeSpaceTextureUrls.left})`
}"
></div>
</div>
</div>
</template>
<script lang="ts">
import {Vr360} from '@nicepkg/vr360-core'
import type {SpaceConfig} from '@nicepkg/vr360-core'
export default {
data() {
// 全景空间配置
const spacesConfig: SpaceConfig[] = [
{
id: 'spaceA', // 空间 id,用于切换空间,必须唯一
tips: [
// 提示,可选
{
id: '1', // 提示 id,用于缓存,在当前 tips 数组里要唯一,必须
position: {x: 0, y: -10, z: 40}, // 提示位置,必须
content: {
// 提示内容,在 showTip 事件会暴露,要包含什么你自己决定。
title: '豪华跑车',
text: '比奥迪还贵的豪华跑车'
}
},
{
id: '2',
// 自定义提示图标贴图,可选
textureUrl:
'https://m.360buyimg.com/babel/jfs/t1/125314/12/31594/6260/6339b149E14068522/5c0d35a3e149936a.png',
targetSpaceId: 'spaceB', // 提示点击后跳转的空间 id,可选
position: {x: -10, y: -4, z: 40},
content: {
title: '去客厅',
text: '一起去尊贵的客厅吧'
}
}
],
cubeSpaceTextureUrls: {
// 立方体贴图,分别为:背侧、下侧、前侧、左侧、右侧、上侧,必须
back: 'https://m.360buyimg.com/babel/jfs/t1/40814/31/19646/41953/63398297E0707fe35/4e831e60cf579899.jpg',
down: 'https://m.360buyimg.com/babel/jfs/t1/43941/23/19369/84038/633982d7E838acd9a/9e7a89cc910d3409.jpg',
front: 'https://m.360buyimg.com/babel/jfs/t1/150573/35/27827/113528/633982faE8556c0c0/b455284fd91885c6.jpg',
left: 'https://m.360buyimg.com/babel/jfs/t1/189204/25/29491/61430/63398310E3c180e43/26d9b459cf714f9f.jpg',
right: 'https://m.360buyimg.com/babel/jfs/t1/184948/34/28448/131232/63398323E61ff80fb/89ab84eda0421260.jpg',
up: 'https://m.360buyimg.com/babel/jfs/t1/86258/24/33602/70616/63398334Ea2dcf448/e2f5c275792fb3d6.jpg'
}
},
{
id: 'spaceB',
tips: [
{
id: '3',
position: {x: -2, y: -25, z: 40},
content: {
title: '香奈儿垃圾桶',
text: '里面装着主人不用的奢侈品'
}
},
{
id: '4',
position: {x: -20, y: 0, z: 40},
content: {
title: '宇宙牌冰箱',
text: '装着超级多零食'
}
},
{
id: '5',
textureUrl:
'https://m.360buyimg.com/babel/jfs/t1/125314/12/31594/6260/6339b149E14068522/5c0d35a3e149936a.png',
targetSpaceId: 'spaceA',
position: {
x: -8,
y: 0,
z: -40
},
content: {
title: '去门口',
text: '一起去门口吧'
}
}
],
cubeSpaceTextureUrls: {
back: 'https://m.360buyimg.com/babel/jfs/t1/48117/28/21445/120448/63398366Ede81497b/a46e362df5f7d0ed.jpg',
down: 'https://m.360buyimg.com/babel/jfs/t1/101209/24/26762/106253/63398376Eedb0db22/4f335c4ecd72ad74.jpg',
front: 'https://m.360buyimg.com/babel/jfs/t1/154056/6/26449/110652/63398388E8ecda044/22e1646534839a95.jpg',
left: 'https://m.360buyimg.com/babel/jfs/t1/198990/28/28201/74687/6339839aE28806a5e/43b311d3379397df.jpg',
right: 'https://m.360buyimg.com/babel/jfs/t1/209711/14/25233/92186/633983b0E8f4df687/750ba84061ea64a6.jpg',
up: 'https://m.360buyimg.com/babel/jfs/t1/186545/35/29054/29678/633983c2E72ef4848/92043b945a03fc29.jpg'
}
}
]
return {
vr360: null as InstanceType<typeof Vr360> | null, // 全景实例
spacesConfig, // 全景空间配置
tip: {
// 提示属性
top: 0, // 提示容器的 top 或 translateY 值
left: 0, // 提示容器的 left 或 translateX 值
title: '', // 提示标题
content: '', // 提示内容
show: false // 是否显示提示
}
}
},
mounted() {
// 初始化全景实例
this.vr360 = new Vr360({
container: this.$refs.containerRef!,
tipContainer: this.$refs.tipRef!,
spacesConfig: this.spacesConfig
})
// 设置全景自动旋转
this.vr360.controls.autoRotate = true
// 开始渲染全景
this.vr360.render()
// 实时监听页面尺寸变化,更新全景尺寸
this.vr360.listenResize()
// 当需要显示提示时
this.vr360.on('showTip', e => {
// 停止旋转场景
this.vr360!.controls.autoRotate = false
// 设置提示内容和提示容器位置
const {top, left, tip} = e
this.tip = {
top,
left,
title: tip.content.title,
content: tip.content.text,
show: true
}
})
// 当需要隐藏提示时
this.vr360.on('hideTip', () => {
// 重新开启旋转场景
this.vr360!.controls.autoRotate = true
// 隐藏提示容器
this.tip.show = false
})
},
destroy() {
// 页面卸载时注销全景实例
this.vr360?.destroy?.()
},
methods: {
// 切换全景空间
handleSwitchSpace(space: SpaceConfig) {
this.vr360?.switchSpace?.(space.id)
}
}
}
</script>
<style>
/* 引入自己的样式 */
@import './demo.css';
</style>
<template>
<div class="demo">
<!-- 提示 -->
<div
ref="tipRef"
class="demo-tip"
:style="{
transform: `translate(${tipLeft}px, ${tipTop + 50}px)`,
zIndex: showTip ? 99 : -1,
visibility: showTip ? 'visible' : 'hidden'
}"
>
<!-- 提示标题 -->
<div class="demo-tip-title">{{ tipTitle }}</div>
<!-- 提示内容 -->
<div class="demo-tip-content">{{ tipContent }}</div>
</div>
<!-- 360全景容器 -->
<div ref="containerRef" class="demo-container"></div>
<!-- 底部切换场景 -->
<div class="demo-bottom-bar">
<div
v-for="space in spacesConfig"
:key="space.id"
class="demo-bottom-card"
@click="handleSwitchSpace(space)"
:style="{
backgroundImage: `url(${space.cubeSpaceTextureUrls.left})`
}"
></div>
</div>
</div>
</template>
<script setup lang="ts">
import {onMounted, onUnmounted, ref} from 'vue'
import {Vr360} from '@nicepkg/vr360-core'
import type {SpaceConfig} from '@nicepkg/vr360-core'
const containerRef = ref<HTMLElement>() // 全景容器
const tipRef = ref<HTMLElement>() // 提示容器
const tipLeft = ref(0) // 提示容器的 left 或 translateX 值
const tipTop = ref(0) // 提示容器的 top 或 translateY 值
const showTip = ref(false) // 是否显示提示容器
const tipTitle = ref('') // 提示容器的标题
const tipContent = ref('') // 提示容器的内容
let vr360: InstanceType<typeof Vr360> // 全景实例
// 全景空间配置
const spacesConfig: SpaceConfig[] = [
{
id: 'spaceA', // 空间 id,用于切换空间,必须唯一
tips: [
// 提示,可选
{
id: '1', // 提示 id,用于缓存,在当前 tips 数组里要唯一,必须
position: {x: 0, y: -10, z: 40}, // 提示位置,必须
content: {
// 提示内容,在 showTip 事件会暴露,要包含什么你自己决定。
title: '豪华跑车',
text: '比奥迪还贵的豪华跑车'
}
},
{
id: '2',
// 自定义提示图标贴图,可选
textureUrl: 'https://m.360buyimg.com/babel/jfs/t1/125314/12/31594/6260/6339b149E14068522/5c0d35a3e149936a.png',
targetSpaceId: 'spaceB', // 提示点击后跳转的空间 id,可选
position: {x: -10, y: -4, z: 40},
content: {
title: '去客厅',
text: '一起去尊贵的客厅吧'
}
}
],
cubeSpaceTextureUrls: {
// 立方体贴图,分别为:背侧、下侧、前侧、左侧、右侧、上侧,必须
back: 'https://m.360buyimg.com/babel/jfs/t1/40814/31/19646/41953/63398297E0707fe35/4e831e60cf579899.jpg',
down: 'https://m.360buyimg.com/babel/jfs/t1/43941/23/19369/84038/633982d7E838acd9a/9e7a89cc910d3409.jpg',
front: 'https://m.360buyimg.com/babel/jfs/t1/150573/35/27827/113528/633982faE8556c0c0/b455284fd91885c6.jpg',
left: 'https://m.360buyimg.com/babel/jfs/t1/189204/25/29491/61430/63398310E3c180e43/26d9b459cf714f9f.jpg',
right: 'https://m.360buyimg.com/babel/jfs/t1/184948/34/28448/131232/63398323E61ff80fb/89ab84eda0421260.jpg',
up: 'https://m.360buyimg.com/babel/jfs/t1/86258/24/33602/70616/63398334Ea2dcf448/e2f5c275792fb3d6.jpg'
}
},
{
id: 'spaceB',
tips: [
{
id: '3',
position: {x: -2, y: -25, z: 40},
content: {
title: '香奈儿垃圾桶',
text: '里面装着主人不用的奢侈品'
}
},
{
id: '4',
position: {x: -20, y: 0, z: 40},
content: {
title: '宇宙牌冰箱',
text: '装着超级多零食'
}
},
{
id: '5',
textureUrl: 'https://m.360buyimg.com/babel/jfs/t1/125314/12/31594/6260/6339b149E14068522/5c0d35a3e149936a.png',
targetSpaceId: 'spaceA',
position: {
x: -8,
y: 0,
z: -40
},
content: {
title: '去门口',
text: '一起去门口吧'
}
}
],
cubeSpaceTextureUrls: {
back: 'https://m.360buyimg.com/babel/jfs/t1/48117/28/21445/120448/63398366Ede81497b/a46e362df5f7d0ed.jpg',
down: 'https://m.360buyimg.com/babel/jfs/t1/101209/24/26762/106253/63398376Eedb0db22/4f335c4ecd72ad74.jpg',
front: 'https://m.360buyimg.com/babel/jfs/t1/154056/6/26449/110652/63398388E8ecda044/22e1646534839a95.jpg',
left: 'https://m.360buyimg.com/babel/jfs/t1/198990/28/28201/74687/6339839aE28806a5e/43b311d3379397df.jpg',
right: 'https://m.360buyimg.com/babel/jfs/t1/209711/14/25233/92186/633983b0E8f4df687/750ba84061ea64a6.jpg',
up: 'https://m.360buyimg.com/babel/jfs/t1/186545/35/29054/29678/633983c2E72ef4848/92043b945a03fc29.jpg'
}
}
]
onMounted(() => {
// 初始化全景实例
vr360 = new Vr360({
container: containerRef.value!,
tipContainer: tipRef.value!,
spacesConfig
})
// 设置全景自动旋转
vr360.controls.autoRotate = true
// 开始渲染全景
vr360.render()
// 实时监听页面尺寸变化,更新全景尺寸
vr360.listenResize()
// 当需要显示提示时
vr360.on('showTip', e => {
// 停止旋转场景
vr360!.controls.autoRotate = false
// 设置提示内容和提示容器位置
const {top, left, tip} = e
showTip.value = true
tipLeft.value = left
tipTop.value = top
tipTitle.value = tip.content.title
tipContent.value = tip.content.text
})
// 当需要隐藏提示时
vr360.on('hideTip', () => {
// 重新开启旋转场景
vr360!.controls.autoRotate = true
// 隐藏提示容器
showTip.value = false
})
})
// 切换全景空间
function handleSwitchSpace(space: SpaceConfig) {
vr360.switchSpace(space.id)
}
onUnmounted(() => {
// 页面卸载时注销全景实例
vr360?.destroy?.()
})
</script>
<style>
/* 引入自己的样式 */
@import './demo.css';
</style>
import React, {useEffect, useState, useRef} from 'react'
import {Vr360} from '@nicepkg/vr360-core'
import type {SpaceConfig} from '@nicepkg/vr360-core'
import './demo.css' // 引入自己的样式
function Example() {
const containerRef = useRef<HTMLDivElement>(null) // 全景容器
const tipRef = useRef<HTMLDivElement>(null) // 提示容器
const [tipLeft, setTipLeft] = useState(0) // 提示容器的 left 或 translateX 值
const [tipTop, setTipTop] = useState(0) // 提示容器的 top 或 translateY 值
const [showTip, setShowTip] = useState(false) // 是否显示提示容器
const [tipTitle, setTipTitle] = useState('') // 提示容器的标题
const [tipContent, setTipContent] = useState('') // 提示容器的内容
const [vr360, setVr360] = useState<InstanceType<typeof Vr360>>() // 全景实例
// 全景空间配置
const spacesConfig: SpaceConfig[] = [
{
id: 'spaceA', // 空间 id,用于切换空间,必须唯一
tips: [
// 提示,可选
{
id: '1', // 提示 id,用于缓存,在当前 tips 数组里要唯一,必须
position: {x: 0, y: -10, z: 40}, // 提示位置,必须
content: {
// 提示内容,在 showTip 事件会暴露,要包含什么你自己决定。
title: '豪华跑车',
text: '比奥迪还贵的豪华跑车'
}
},
{
id: '2',
// 自定义提示图标贴图,可选
textureUrl:
'https://m.360buyimg.com/babel/jfs/t1/125314/12/31594/6260/6339b149E14068522/5c0d35a3e149936a.png',
targetSpaceId: 'spaceB', // 提示点击后跳转的空间 id,可选
position: {x: -10, y: -4, z: 40},
content: {
title: '去客厅',
text: '一起去尊贵的客厅吧'
}
}
],
cubeSpaceTextureUrls: {
// 立方体贴图,分别为:背侧、下侧、前侧、左侧、右侧、上侧,必须
back: 'https://m.360buyimg.com/babel/jfs/t1/40814/31/19646/41953/63398297E0707fe35/4e831e60cf579899.jpg',
down: 'https://m.360buyimg.com/babel/jfs/t1/43941/23/19369/84038/633982d7E838acd9a/9e7a89cc910d3409.jpg',
front: 'https://m.360buyimg.com/babel/jfs/t1/150573/35/27827/113528/633982faE8556c0c0/b455284fd91885c6.jpg',
left: 'https://m.360buyimg.com/babel/jfs/t1/189204/25/29491/61430/63398310E3c180e43/26d9b459cf714f9f.jpg',
right: 'https://m.360buyimg.com/babel/jfs/t1/184948/34/28448/131232/63398323E61ff80fb/89ab84eda0421260.jpg',
up: 'https://m.360buyimg.com/babel/jfs/t1/86258/24/33602/70616/63398334Ea2dcf448/e2f5c275792fb3d6.jpg'
}
},
{
id: 'spaceB',
tips: [
{
id: '3',
position: {x: -2, y: -25, z: 40},
content: {
title: '香奈儿垃圾桶',
text: '里面装着主人不用的奢侈品'
}
},
{
id: '4',
position: {x: -20, y: 0, z: 40},
content: {
title: '宇宙牌冰箱',
text: '装着超级多零食'
}
},
{
id: '5',
textureUrl:
'https://m.360buyimg.com/babel/jfs/t1/125314/12/31594/6260/6339b149E14068522/5c0d35a3e149936a.png',
targetSpaceId: 'spaceA',
position: {
x: -8,
y: 0,
z: -40
},
content: {
title: '去门口',
text: '一起去门口吧'
}
}
],
cubeSpaceTextureUrls: {
back: 'https://m.360buyimg.com/babel/jfs/t1/48117/28/21445/120448/63398366Ede81497b/a46e362df5f7d0ed.jpg',
down: 'https://m.360buyimg.com/babel/jfs/t1/101209/24/26762/106253/63398376Eedb0db22/4f335c4ecd72ad74.jpg',
front: 'https://m.360buyimg.com/babel/jfs/t1/154056/6/26449/110652/63398388E8ecda044/22e1646534839a95.jpg',
left: 'https://m.360buyimg.com/babel/jfs/t1/198990/28/28201/74687/6339839aE28806a5e/43b311d3379397df.jpg',
right: 'https://m.360buyimg.com/babel/jfs/t1/209711/14/25233/92186/633983b0E8f4df687/750ba84061ea64a6.jpg',
up: 'https://m.360buyimg.com/babel/jfs/t1/186545/35/29054/29678/633983c2E72ef4848/92043b945a03fc29.jpg'
}
}
]
useEffect(() => {
// 初始化全景实例
setVr360(
new Vr360({
container: containerRef.current!,
tipContainer: tipRef.current!,
spacesConfig
})
)
return () => {
// 页面卸载时注销全景实例
vr360?.destroy?.()
}
}, [])
useEffect(() => {
if (vr360) {
// 设置全景自动旋转
vr360.controls.autoRotate = true
// 开始渲染全景
vr360.render()
// 实时监听页面尺寸变化,更新全景尺寸
vr360.listenResize()
// 当需要显示提示时
vr360.on('showTip', e => {
// 停止旋转场景
vr360!.controls.autoRotate = false
// 设置提示内容和提示容器位置
const {top, left, tip} = e
setShowTip(true)
setTipLeft(left)
setTipTop(top)
setTipTitle(tip.content.title)
setTipContent(tip.content.text)
})
// 当需要隐藏提示时
vr360.on('hideTip', () => {
// 重新开启旋转场景
vr360!.controls.autoRotate = true
// 隐藏提示容器
setShowTip(false)
})
}
}, [vr360])
// 切换全景空间
function handleSwitchSpace(space: SpaceConfig) {
vr360?.switchSpace?.(space.id)
}
return (
<div className="demo">
{/* 提示 */}
<div
ref={tipRef}
className="demo-tip"
style={{
transform: `translate(${tipLeft}px, ${tipTop + 50}px)`,
zIndex: showTip ? 99 : -1,
visibility: showTip ? 'visible' : 'hidden'
}}
>
{/* 提示标题 */}
<div className="demo-tip-title">{tipTitle}</div>
{/* 提示内容 */}
<div className="demo-tip-content">{tipContent}</div>
</div>
{/* 360全景容器 */}
<div ref={containerRef} className="demo-container"></div>
{/* 底部切换场景 */}
<div className="demo-bottom-bar">
{spacesConfig.map(space => (
<div
key={space.id}
className="demo-bottom-card"
onClick={() => handleSwitchSpace(space)}
style={{
backgroundImage: `url(${space.cubeSpaceTextureUrls.left})`
}}
></div>
))}
</div>
</div>
)
}
export default Example
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>Html App</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
/>
<!-- 引入 threejs -->
<script src="https://unpkg.com/three@0.145.0/build/three.min.js"></script>
<!-- 引入 vr360-core -->
<script src="https://unpkg.com/@nicepkg/vr360-core@^0.3.1/dist/index.min.umd.js"></script>
<!-- 引入自己的 css -->
<link href="./demo.css" rel="stylesheet" />
</head>
<body>
<div class="demo">
<!-- 提示 -->
<div class="demo-tip">
<!-- 提示标题 -->
<div class="demo-tip-title"></div>
<!-- 提示内容 -->
<div class="demo-tip-content"></div>
</div>
<!-- 360全景容器 -->
<div class="demo-container"></div>
<!-- 底部切换场景 -->
<div class="demo-bottom-bar"></div>
</div>
<script>
const {Vr360} = Vr360Core // 原生使用时,所有的导出都在 window.Vr360Core 里
const container = document.querySelector('.demo-container') // 360全景容器
const tip = document.querySelector('.demo-tip') // 提示容器
const tipTitle = document.querySelector('.demo-tip-title') // 提示标题 el
const tipContent = document.querySelector('.demo-tip-content') // 提示内容 el
const bottomBar = document.querySelector('.demo-bottom-bar') // 底部切换场景容器
// 全景空间配置
const spacesConfig = [
{
id: 'spaceA', // 空间 id,用于切换空间,必须唯一
tips: [
// 提示,可选
{
id: '1', // 提示 id,用于缓存,在当前 tips 数组里要唯一,必须
position: {x: 0, y: -10, z: 40}, // 提示位置,必须
content: {
// 提示内容,在 showTip 事件会暴露,要包含什么你自己决定。
title: '豪华跑车',
text: '比奥迪还贵的豪华跑车'
}
},
{
id: '2',
// 自定义提示图标贴图,可选
textureUrl:
'https://m.360buyimg.com/babel/jfs/t1/125314/12/31594/6260/6339b149E14068522/5c0d35a3e149936a.png',
targetSpaceId: 'spaceB', // 提示点击后跳转的空间 id,可选
position: {x: -10, y: -4, z: 40},
content: {
title: '去客厅',
text: '一起去尊贵的客厅吧'
}
}
],
cubeSpaceTextureUrls: {
// 立方体贴图,分别为:背侧、下侧、前侧、左侧、右侧、上侧,必须
back: 'https://m.360buyimg.com/babel/jfs/t1/40814/31/19646/41953/63398297E0707fe35/4e831e60cf579899.jpg',
down: 'https://m.360buyimg.com/babel/jfs/t1/43941/23/19369/84038/633982d7E838acd9a/9e7a89cc910d3409.jpg',
front: 'https://m.360buyimg.com/babel/jfs/t1/150573/35/27827/113528/633982faE8556c0c0/b455284fd91885c6.jpg',
left: 'https://m.360buyimg.com/babel/jfs/t1/189204/25/29491/61430/63398310E3c180e43/26d9b459cf714f9f.jpg',
right: 'https://m.360buyimg.com/babel/jfs/t1/184948/34/28448/131232/63398323E61ff80fb/89ab84eda0421260.jpg',
up: 'https://m.360buyimg.com/babel/jfs/t1/86258/24/33602/70616/63398334Ea2dcf448/e2f5c275792fb3d6.jpg'
}
},
{
id: 'spaceB',
tips: [
{
id: '3',
position: {x: -2, y: -25, z: 40},
content: {
title: '香奈儿垃圾桶',
text: '里面装着主人不用的奢侈品'
}
},
{
id: '4',
position: {x: -20, y: 0, z: 40},
content: {
title: '宇宙牌冰箱',
text: '装着超级多零食'
}
},
{
id: '5',
textureUrl:
'https://m.360buyimg.com/babel/jfs/t1/125314/12/31594/6260/6339b149E14068522/5c0d35a3e149936a.png',
targetSpaceId: 'spaceA',
position: {
x: -8,
y: 0,
z: -40
},
content: {
title: '去门口',
text: '一起去门口吧'
}
}
],
cubeSpaceTextureUrls: {
back: 'https://m.360buyimg.com/babel/jfs/t1/48117/28/21445/120448/63398366Ede81497b/a46e362df5f7d0ed.jpg',
down: 'https://m.360buyimg.com/babel/jfs/t1/101209/24/26762/106253/63398376Eedb0db22/4f335c4ecd72ad74.jpg',
front: 'https://m.360buyimg.com/babel/jfs/t1/154056/6/26449/110652/63398388E8ecda044/22e1646534839a95.jpg',
left: 'https://m.360buyimg.com/babel/jfs/t1/198990/28/28201/74687/6339839aE28806a5e/43b311d3379397df.jpg',
right: 'https://m.360buyimg.com/babel/jfs/t1/209711/14/25233/92186/633983b0E8f4df687/750ba84061ea64a6.jpg',
up: 'https://m.360buyimg.com/babel/jfs/t1/186545/35/29054/29678/633983c2E72ef4848/92043b945a03fc29.jpg'
}
}
]
// 初始化全景实例
const vr360 = new Vr360({
container, // 全景挂载容器
tipContainer: tip, // 提示挂载容器
spacesConfig // 全景配置
})
// 设置全景自动旋转
vr360.controls.autoRotate = true
// 开始渲染全景
vr360.render()
// 实时监听页面尺寸变化,更新全景尺寸
vr360.listenResize()
// 当需要显示提示时
vr360.on('showTip', e => {
// 停止旋转场景
vr360.controls.autoRotate = false
// 设置提示内容和提示容器位置
const {top, left} = e
Object.assign(tip.style, {
transform: `translate(${left}px, ${top + 50}px)`,
zIndex: 99,
visibility: 'visible'
})
tip.style.t
tipTitle.innerText = e.tip.content.title
tipContent.innerText = e.tip.content.text
})
// 当需要隐藏提示时
vr360.on('hideTip', () => {
// 重新开启旋转场景
vr360.controls.autoRotate = true
// 隐藏提示容器
tip.style.zIndex = -1
tip.style.visibility = 'hidden'
})
// 页面卸载时注销全景实例
window.addEventListener('unload', () => {
vr360.destroy()
})
// 切换全景空间
function handleSwitchSpace(space) {
vr360.switchSpace(space.id)
}
bottomBar.append(
...spacesConfig.map(space => {
const card = document.createElement('div')
card.className = 'demo-bottom-card'
Object.assign(card.style, {
backgroundImage: `url(${space.cubeSpaceTextureUrls.left})`
})
card.addEventListener('click', () => {
handleSwitchSpace(space)
})
return card
})
)
</script>
</body>
</html>
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
.demo {
position: relative;
display: flex;
flex-direction: column;
width: 100vw;
height: 100vh;
overflow: hidden;
background-color: #fff;
}
.demo-tip {
position: absolute;
top: 0;
left: 0;
z-index: -1;
display: flex;
flex-direction: column;
justify-content: center;
width: 240px;
height: 60px;
padding: 4px;
color: #fff;
cursor: pointer;
visibility: hidden;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 4px;
}
.demo-tip-title {
font-weight: bold;
}
.demo-container {
width: 100%;
height: 100%;
}
.demo-bottom-bar {
display: flex;
align-items: center;
width: 100%;
height: 100px;
}
.demo-bottom-card {
width: 140px;
height: 70px;
margin-left: 1rem;
cursor: pointer;
background-repeat: no-repeat;
background-size: cover;
border-radius: 4px;
}