跳到主要内容

全景热点标签

PanoTagPlugin

功能说明

全景热点标签插件 提供在全景模式下标注房源不同位置热点标签信息。

详细功能点如下:

  • 热点标签共分为“音频标签(Audio)”、“文本标签(Text)”、“图文标签(ImageText)”、“VR 跳转标签(Link)”、“营销标签(Marketing)”、“图片视频贴片(MediaPlane)”、“自定义标签(Custom)”等。
  • 标签按照维度类型(DimensionType)可以分为:“2D(Two)”和“3D(Three)”两种。
  • 按照点位类型(PointType)来分,标签又可以分为:点标记(PointTag)和平面标记(PlaneTag)两种。
  • 用户可以自由组合上述标签分类属性,根据自己的业务类型,创造更加适合的全景热点标签。

示例效果

安装引用

请按需选择 yarnnpm 安装方式:

npm install @realsee/dnalogel

通过 es 引用:

import { PanoTagPlugin } from '@realsee/dnalogel'

开发指南

初始化

在初始化 Five 实例时,将 PanoTagPlugin 配置在初始化插件参数即可。

import { Five } from '@realsee/five'
import { PanoTagPlugin } from '@realsee/dnalogel'

const five = new Five({
  plugins: [
    [
      PanoTagPlugin,
      'panoTagPlugin', // 自定义插件名称
      {
        // 参数配置
      },
    ],
  ],
})

React 初始化

在创建 FiveProvider 时,将 PanoTagPlugin 配置在初始化插件参数即可。

import { PanoTagPlugin } from '@realsee/dnalogel'
import { createFiveProvider, FiveCanvas } from '@realsee/five/react'

const FiveProvider = createFiveProvider({
  plugins: [
    [
      PanoTagPlugin,
      'panoTagPlugin', // 自定义插件名称
      {
        // 参数配置
      },
    ],
  ],
})

Vue 初始化

在使用 FiveProvider 时,将 PanoTagPlugin 配置在初始化插件参数即可。

<template>
  <FiveProvider :fiveInitArgs="fiveInitArgs"> </FiveProvider>
</template>
<script setup>
import PanoTagPlugin from '@realsee/dnalogel/libs/PanoTagPlugin'
import { FiveProvider, FiveCanvas } from '@realsee/five/vue'
const fiveInitArgs = {
  plugins: [
    [
      PanoTagPlugin,
      'panoTagPlugin', // 自定义插件名称
      {
        // 参数配置
      },
    ],
  ],
}
</script>

载入数据

// 获取插件实例,其中 `panoTagPlugin` 是初始化时自定义的名称
const pluginInstance = five.plugins.panoTagPlugin

// 调用 `load` 方法载入全景标签数据
pluginInstance.load(tagsData, config)

核心方法

  • load:(data: Tags, config?: AddTagConfig) => void 载入插件数据

  • addTag:(tag: Tag | Tag[], config?: AddTagConfig) => void 添加标签

  • clearTags: () => void 清空所有标签

  • async show: (params?: { userAction?: boolean; withAnimation?: boolean }) => void 显示标签

  • async hide: (params?: { userAction?: boolean; withAnimation?: boolean }) => void 隐藏标签

  • enable: (params?: { userAction?: boolean }) => void 允许使用插件

  • disable: (params?: { userAction?: boolean }) => void 禁止使用插件

  • dispose: () => void 销毁插件

  • setState: (state: Partial<State>, params: { userAction?: boolean; visibleWithAnimation?: boolean }) => void 更新插件状态

  • registerRenderer: (contentType: string, renderer: ElementRenderer, config?: { usePoint?: boolean }) => (() => any) 注册标签渲染器

  • changeTagNormalById: (id: TagId, normal: ArrayPosition) => void 修改标签位置信息

  • changeDataById: (id: TagId, data: PartialDeep<Tag<C>['data']>, deepMerge = true) => void 改变 data

  • changeTagById: (id: TagId,tag: PartialDeep<{ [P in keyof Pick<Tag<C>, 'enabled' | 'style' | 'dimensionType' | 'contentType' | 'data' | 'normal'>]: Tag[P] }>,deepMerge = true,) => void 改变 tag 任意属性

  • destroyTagById: (id: TagId | TagId[]) => void 销毁 tag

  • pauseCurrentMedia: () => void 暂停当前标签内进行的所有多媒体

热点标签配置

热点标签可以通过丰富的配置来实现各种不同的标签展示效果:

添加 config

  1. load 时添加
const const globalConfig: TagConfig<"Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown">globalConfig: interface TagConfig<C extends "Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown" = "Audio" | ... 14 more ... | "Unknown">TagConfig = {}
const const TextTagConfig: TagConfig<"Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown">TextTagConfig: interface TagConfig<C extends "Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown" = "Audio" | ... 14 more ... | "Unknown">TagConfig = {}

const pluginInstance: PanoTagPluginControllerpluginInstance.PanoTagPluginController.load(data: Tags, config?: AddTagConfig | undefined): Promise<void>
@description

: 加载数据

load
({
Tags.tagList: Tag<"Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown", StickType>[]
标签列表配置
tagList
: [],
Tags.globalConfig?: TagConfig<"Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown"> | undefined
全局配置
globalConfig
: const globalConfig: TagConfig<"Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown">globalConfig,
Tags.contentTypeConfig?: {
[x: `Mixin-${string}`]: TagConfig<any> | undefined;
[x: `[Model]-Mixin-${string}`]: TagConfig<any> | undefined;
[x: `[Panorama]-Mixin-${string}`]: TagConfig<any> | undefined;
[x: `[Floorplan]-Mixin-${string}`]: TagConfig<any> | undefined;
[x: `[Topview]-Mixin-${string}`]: TagConfig<any> | undefined;
[x: `[Mapview]-Mixin-${string}`]: TagConfig<any> | undefined;
[x: `[VRPanorama]-Mixin-${string}`]: TagConfig<any> | undefined;
[x: `[DepthPanorama]-Mixin-${string}`]: TagConfig<any> | undefined;
[x: `[XRPanorama]-Mixin-${string}`]: TagConfig<any> | undefined;
[x: `[PanoramaLike]-Mixin-${string}`]: TagConfig<any> | undefined;
[x: `[ModelLike]-Mixin-${string}`]: TagConfig<any> | undefined;
Audio?: TagConfig<"Audio"> | undefined;
Text?: TagConfig<"Text"> | undefined;
ImageText?: TagConfig<"ImageText"> | undefined;
Image?: TagConfig<"Image"> | undefined;
Video?: TagConfig<"Video"> | undefined;
Link?: TagConfig<"Link"> | undefined;
Sticker?: TagConfig<"Sticker"> | undefined;
VRLink?: TagConfig<"VRLink"> | undefined;
PanoLink?: TagConfig<"PanoLink"> | undefined;
Marketing?: TagConfig<"Marketing"> | undefined;
MediaPlane?: TagConfig<"MediaPlane"> | undefined;
MediaModel?: TagConfig<"MediaModel"> | undefined;
Model?: TagConfig<"Model"> | undefined;
Panorama?: TagConfig<"Panorama"> | undefined;
Custom?: TagConfig<"Custom"> | undefined;
Unknown?: TagConfig<"Unknown"> | undefined;
"[Model]"?: TagConfig<never> | undefined;
"[Panorama]"?: TagConfig<never> | undefined;
"[Floorplan]" ...
按type配置
contentTypeConfig
: {
'Text': const TextTagConfig: TagConfig<"Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown">TextTagConfig } })
  1. 实时切换
import { interface TagConfig<C extends "Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown" = "Audio" | ... 14 more ... | "Unknown">TagConfig } from '@realsee/dnalogel'

const const globalConfig: TagConfig<"Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown">globalConfig: interface TagConfig<C extends "Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown" = "Audio" | ... 14 more ... | "Unknown">TagConfig = {}
const const contentTypeConfig: {
Text: {};
}
contentTypeConfig
= {
'Text': {} } const pluginInstance: PanoTagPluginControllerpluginInstance.PanoTagPluginController.changeConfig(config: Pick<Tags, "globalConfig" | "contentTypeConfig">, merge?: boolean | undefined): void
@description

: 改变配置

changeConfig
({
globalConfig?: TagConfig<"Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown"> | undefined
全局配置
globalConfig
,
contentTypeConfig?: {
[x: `Mixin-${string}`]: TagConfig<any> | undefined;
[x: `[Model]-Mixin-${string}`]: TagConfig<any> | undefined;
[x: `[Panorama]-Mixin-${string}`]: TagConfig<any> | undefined;
[x: `[Floorplan]-Mixin-${string}`]: TagConfig<any> | undefined;
[x: `[Topview]-Mixin-${string}`]: TagConfig<any> | undefined;
[x: `[Mapview]-Mixin-${string}`]: TagConfig<any> | undefined;
[x: `[VRPanorama]-Mixin-${string}`]: TagConfig<any> | undefined;
[x: `[DepthPanorama]-Mixin-${string}`]: TagConfig<any> | undefined;
[x: `[XRPanorama]-Mixin-${string}`]: TagConfig<any> | undefined;
[x: `[PanoramaLike]-Mixin-${string}`]: TagConfig<any> | undefined;
[x: `[ModelLike]-Mixin-${string}`]: TagConfig<any> | undefined;
Audio?: TagConfig<"Audio"> | undefined;
Text?: TagConfig<"Text"> | undefined;
ImageText?: TagConfig<"ImageText"> | undefined;
Image?: TagConfig<"Image"> | undefined;
Video?: TagConfig<"Video"> | undefined;
Link?: TagConfig<"Link"> | undefined;
Sticker?: TagConfig<"Sticker"> | undefined;
VRLink?: TagConfig<"VRLink"> | undefined;
PanoLink?: TagConfig<"PanoLink"> | undefined;
Marketing?: TagConfig<"Marketing"> | undefined;
MediaPlane?: TagConfig<"MediaPlane"> | undefined;
MediaModel?: TagConfig<"MediaModel"> | undefined;
Model?: TagConfig<"Model"> | undefined;
Panorama?: TagConfig<"Panorama"> | undefined;
Custom?: TagConfig<"Custom"> | undefined;
Unknown?: TagConfig<"Unknown"> | undefined;
"[Model]"?: TagConfig<never> | undefined;
"[Panorama]"?: TagConfig<never> | undefined;
"[Floorplan]" ...
按type配置
contentTypeConfig
})

Config 种类

1. 全局 Config

const const globalConfig: TagConfig<"Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown">globalConfig: interface TagConfig<C extends "Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown" = "Audio" | ... 14 more ... | "Unknown">TagConfig = {}

const pluginInstance: PanoTagPluginControllerpluginInstance.PanoTagPluginController.load(data: Tags, config?: AddTagConfig | undefined): Promise<void>
@description

: 加载数据

load
({
Tags.tagList: Tag<"Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown", StickType>[]
标签列表配置
tagList
: [],
Tags.globalConfig?: TagConfig<"Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown"> | undefined
全局配置
globalConfig
: const globalConfig: TagConfig<"Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown">globalConfig
})

2. 按标签类型的config,只对指定类型的标签生效

这种设置方式全局配置也会同时生效,配置冲突时以局部配置为准

const const TextTagConfig: TagConfig<"Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown">TextTagConfig: interface TagConfig<C extends "Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown" = "Audio" | ... 14 more ... | "Unknown">TagConfig = {}

const pluginInstance: PanoTagPluginControllerpluginInstance.PanoTagPluginController.load(data: Tags, config?: AddTagConfig | undefined): Promise<void>
@description

: 加载数据

load
({
Tags.tagList: Tag<"Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown", StickType>[]
标签列表配置
tagList
: [],
Tags.contentTypeConfig?: {
[x: `Mixin-${string}`]: TagConfig<any> | undefined;
[x: `[Model]-Mixin-${string}`]: TagConfig<any> | undefined;
[x: `[Panorama]-Mixin-${string}`]: TagConfig<any> | undefined;
[x: `[Floorplan]-Mixin-${string}`]: TagConfig<any> | undefined;
[x: `[Topview]-Mixin-${string}`]: TagConfig<any> | undefined;
[x: `[Mapview]-Mixin-${string}`]: TagConfig<any> | undefined;
[x: `[VRPanorama]-Mixin-${string}`]: TagConfig<any> | undefined;
[x: `[DepthPanorama]-Mixin-${string}`]: TagConfig<any> | undefined;
[x: `[XRPanorama]-Mixin-${string}`]: TagConfig<any> | undefined;
[x: `[PanoramaLike]-Mixin-${string}`]: TagConfig<any> | undefined;
[x: `[ModelLike]-Mixin-${string}`]: TagConfig<any> | undefined;
Audio?: TagConfig<"Audio"> | undefined;
Text?: TagConfig<"Text"> | undefined;
ImageText?: TagConfig<"ImageText"> | undefined;
Image?: TagConfig<"Image"> | undefined;
Video?: TagConfig<"Video"> | undefined;
Link?: TagConfig<"Link"> | undefined;
Sticker?: TagConfig<"Sticker"> | undefined;
VRLink?: TagConfig<"VRLink"> | undefined;
PanoLink?: TagConfig<"PanoLink"> | undefined;
Marketing?: TagConfig<"Marketing"> | undefined;
MediaPlane?: TagConfig<"MediaPlane"> | undefined;
MediaModel?: TagConfig<"MediaModel"> | undefined;
Model?: TagConfig<"Model"> | undefined;
Panorama?: TagConfig<"Panorama"> | undefined;
Custom?: TagConfig<"Custom"> | undefined;
Unknown?: TagConfig<"Unknown"> | undefined;
"[Model]"?: TagConfig<never> | undefined;
"[Panorama]"?: TagConfig<never> | undefined;
"[Floorplan]" ...
按type配置
contentTypeConfig
: {
'Text': const TextTagConfig: TagConfig<"Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown">TextTagConfig } })

2. 标签单独配置,只对单个标签生效

这种设置方式全局配置和标签类型维度的配置也会同时生效,配置冲突时以标签单独配置为准

const const TagConfig1: TagConfig<"Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown">TagConfig1: interface TagConfig<C extends "Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown" = "Audio" | ... 14 more ... | "Unknown">TagConfig = {}

const pluginInstance: PanoTagPluginControllerpluginInstance.PanoTagPluginController.load(data: Tags, config?: AddTagConfig | undefined): Promise<void>
@description

: 加载数据

load
({
Tags.tagList: Tag<"Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown", StickType>[]
标签列表配置
tagList
: [
{ ...let tagData: anytagData, // 标签数据 config?: TagConfig<"Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown"> | undefined
「展开/收起」 「可见/不可见」 的策略配置
config
: const TagConfig1: TagConfig<"Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown">TagConfig1 // 只针对本标签有效
} ], })

Config interface

const const config: {
visibleConfig?: {
keep?: "visible" | "hidden" | null | undefined;
visibleFiveMode?: TagVisibleMode | ((tag: TagInstance) => TagVisibleMode) | undefined;
followModelVisibility?: boolean | undefined;
alwaysShowWhenMovePano?: boolean | undefined;
visibleDistance?: MinMax | "unLimited" | undefined;
visiblePanoIndex?: "all" | number[] | "current" | undefined;
intersectRaycaster?: boolean | {
enabled?: boolean | undefined;
distanceAccuracy?: number | undefined;
checkPoints?: "center" | "corner" | Vector3[] | undefined;
needPassed?: number | undefined;
} | undefined;
angleRange?: MinMax | undefined;
} | ConfigFunction | undefined;
unfoldedConfig?: ConfigFunction | {
keep?: "unfolded" | "folded" | null | undefined;
autoFoldWhenHide?: false | undefined;
disableUnfold?: true | undefined;
disableFold?: true | undefined;
unfoldDistance?: MinMax | undefined;
autoUnfold?: false | {
enable?: boolean | undefined;
strategy?: "ScreenPostion" | undefined;
autoUnfoldProjectX?: MinMax | undefined;
} | {
enable?: boolean | undefined;
strategy?: "MinimumDistance" | undefined;
maxNumber?: number | undefined;
distance?: MinMax | undefined;
} | undefined;
} | undefined;
initialState?: {
visible?: boolean | undefined;
unfolded?: boolean | undefined;
} | undefined;
initialData?: (PartialObjectDeep ...
config
: type Simplify<T> = { [KeyType in keyof T]: T[KeyType]; }
Useful to flatten the type output to improve type hints shown in editors. And also to transform an interface into a type to aide with assignability.
@example
import type {Simplify} from 'type-fest';

type PositionProps = {
top: number;
left: number;
};

type SizeProps = {
width: number;
height: number;
};

// In your editor, hovering over `Props` will show a flattened object with all the properties.
type Props = Simplify<PositionProps & SizeProps>;

Sometimes it is desired to pass a value as a function argument that has a different type. At first inspection it may seem assignable, and then you discover it is not because the value's type definition was defined as an interface. In the following example, fn requires an argument of type Record<string, unknown>. If the value is defined as a literal, then it is assignable. And if the value is defined as type using the Simplify utility the value is assignable. But if the value is defined as an interface, it is not assignable because the interface is not sealed and elsewhere a non-string property could be added to the interface.

If the type definition must be an interface (perhaps it was defined in a third-party npm package), then the value can be defined as const value: Simplify<SomeInterface> = .... Then value will be assignable to the fn argument. Or the value can be cast as Simplify<SomeInterface> if you can't re-declare the value.

@example
import type {Simplify} from 'type-fest';

interface SomeInterface {
foo: number;
bar?: string;
baz: number | undefined;
}

type SomeType = {
foo: number;
bar?: string;
baz: number | undefined;
};

const literal = {foo: 123, bar: 'hello', baz: 456};
const someType: SomeType = literal;
const someInterface: SomeInterface = literal;

function fn(object: Record<string, unknown>): void {}

fn(literal); // Good: literal object type is sealed
fn(someType); // Good: type is sealed
fn(someInterface); // Error: Index signature for type 'string' is missing in type 'someInterface'. Because `interface` can be re-opened
fn(someInterface as Simplify<SomeInterface>); // Good: transform an `interface` into a `type`
@link

https://github.com/microsoft/TypeScript/issues/15300

@category

Object

Simplify
<interface TagConfig<C extends "Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown" = "Audio" | ... 14 more ... | "Unknown">TagConfig> = {
visibleConfig?: {
keep?: "visible" | "hidden" | null | undefined;
visibleFiveMode?: TagVisibleMode | ((tag: TagInstance) => TagVisibleMode) | undefined;
followModelVisibility?: boolean | undefined;
alwaysShowWhenMovePano?: boolean | undefined;
visibleDistance?: MinMax | "unLimited" | undefined;
visiblePanoIndex?: "all" | number[] | "current" | undefined;
intersectRaycaster?: boolean | {
enabled?: boolean | undefined;
distanceAccuracy?: number | undefined;
checkPoints?: "center" | "corner" | Vector3[] | undefined;
needPassed?: number | undefined;
} | undefined;
angleRange?: MinMax | undefined;
} | ConfigFunction | undefined
@description

: 显示/隐藏相关配置

visibleConfig
: {
keep?: "visible" | "hidden" | null | undefined
@description

visibleFiveMode 指定的 mode 中,保持可见/不可见。设置后 visibleConfig 中除了 visibleFiveMode 外的所有其他配置都不生效

@default

null

@example
// 在 Panorama 和 Floorplan 模式下永远保持可见
const config = {
 keep: 'visible',
 visibleFiveMode: ['Panorama', 'Floorplan'],
}
// 在 tag.fiveState.mode 中时,永远保持可见
const config = {
 keep: 'visible',
}
keep
: null,
visibleFiveMode?: TagVisibleMode | ((tag: TagInstance) => TagVisibleMode) | undefined
@description

在哪些 five mode 下可见

@tips

默认值的设定是在 config 合并后,而不是合并前

@default
 visibleFiveMode = tag.fiveState?.mode ?? 'Panorama'
@example
const tag = {
 ...
 fiveState: {
   mode: 'Floorplan',
 }
 ...
 config: {},
}
// 相当于
const tag = {
 ...
 fiveState: {
   mode: 'Floorplan',
 }
 ...
 config: {
   visibleFiveMode: 'Floorplan',
 },
}
visibleFiveMode
: ['Panorama'],
visibleDistance?: MinMax | "unLimited" | undefined
@description

配置可见距离

@fivemode

只在全景模式下生效

@default

'unLimited'

visibleDistance
: { MinMax.min?: number | undefinedmin: 0, MinMax.max?: number | undefinedmax: 10 },
visiblePanoIndex?: "all" | number[] | "current" | undefined
@description

配置标签可见点位

@fivemode

只在全景模式下生效

@param

all 不限制

@param

current 仅当前点位可见

@param

number 仅指定点位可见

@default

'all'

visiblePanoIndex
: var undefinedundefined,
}, unfoldedConfig?: ConfigFunction | {
keep?: "unfolded" | "folded" | null | undefined;
autoFoldWhenHide?: false | undefined;
disableUnfold?: true | undefined;
disableFold?: true | undefined;
unfoldDistance?: MinMax | undefined;
autoUnfold?: false | {
enable?: boolean | undefined;
strategy?: "ScreenPostion" | undefined;
autoUnfoldProjectX?: MinMax | undefined;
} | {
enable?: boolean | undefined;
strategy?: "MinimumDistance" | undefined;
maxNumber?: number | undefined;
distance?: MinMax | undefined;
} | undefined;
} | undefined
@description

: 展开/收起相关配置

unfoldedConfig
: {
keep?: "unfolded" | "folded" | null | undefined
@description

: 保持展开/收起,设置后unfoldedConfig下所有其他配置都不生效

@default

null

keep
: null,
autoFoldWhenHide?: false | undefined
@description

划动到不可见状态时,自动收起,disableFold: true 的标签不受此参数影响

@note

部分标签是无法收起的

@default

true

autoFoldWhenHide
: false,
autoUnfold?: false | {
enable?: boolean | undefined;
strategy?: "ScreenPostion" | undefined;
autoUnfoldProjectX?: MinMax | undefined;
} | {
enable?: boolean | undefined;
strategy?: "MinimumDistance" | undefined;
maxNumber?: number | undefined;
distance?: MinMax | undefined;
} | undefined
自动展开策略, false 时不自动展开
autoUnfold
: {
enable?: boolean | undefined
enable
: true,
strategy?: "MinimumDistance" | undefined
自动展开:最近标签自动展开
strategy
: 'MinimumDistance',
maxNumber?: number | undefined
@description

: 展开的最大数量

@default

1

maxNumber
: 1,
distance?: MinMax | undefined
@description

: 展开的最大距离

@default

无限制

distance
: { MinMax.min?: number | undefinedmin: 0, MinMax.max?: number | undefinedmax: 10 }
} } }

添加自定义热点标签

热点标签中有一个标签类型叫做“自定义热点标签”,使用这个标签类型,开发者可以根据自己的业务需要,自定义添加任意符合规范的标签样式。

可以参考下面的例子:

// 添加自定义热点
const addCustomerTag = () => {
  // 自定义Element
  const ele = document.createElement('div')
  ele.innerText = '这是一个自定义的热点标签'
  ele.style.color = 'red'
  ele.style.width = '200px'
  ele.style.border = '1px solid #000'

  const tagData: Tag = {
    id: '03338b76-b64a-4e90-37fb-44e3c0ffeb88',
    pointType: 'PointTag',
    dimensionType: '2D',
    position: [-1.7882169929208833, 1.022040232156752, -2.339700937271118],
    data: {
      text: '自定义热点标签',
    },
    element: ele,
    // ContentType设置为Custom
    contentType: 'Custom',
  }
  pluginInstance.addTag(tagData)
}

自定义标签渲染器

1. 指定自定义标签类型的渲染方式

使用场景:

我通过某种方式生产了一批 contentType 为 super_tag 的标签数据,我现在想用 PanoTagPlugin 来渲染这批数据,显示结果为:<div>I'm super tag, my name is {data.name}</div>

代码示例:

const plugin = five.plugins.panoTagPlugin

/** 自定义标签组件 */
function SuperTag(props: { tag: TagInstance }) {
  return <div>I'm a super tag, my name is {props.tag.data.name}</div>
}

plugin.registerRenderer('super_tag', (container: HTMLDivElement, tagInstance: TagInstance) => {
  // 使用 <SuperTag> 来渲染 contentType 为 "super_tag" 的标签
  ReactDOM.render(<SuperTag tag={tag}></SuperTag>, container)
  // 需返回销毁函数
  return () => ReactDOM.unmountComponentAtNode(container)
})

// 自定义标签数据
plugin.load({
  tagList: [
    {
      contentType: 'super_tag',
      position: [0, 1, 2],
      data: {
        name: 'super tag 1',
      },
    },
  ],
})

Tips: plugin.registerRenderer 调用一次即可,无需多次调用。若不确定是否已经调用过,可以使用 if (plugin.renderMap.has('My_Custom_Tag_Type')) {} 判断渲染器是否已被注册

2. 替换已有标签的渲染方式

使用场景:

我觉得PanoTagPlugin里的文本标签(contentType: 'Text')太丑了,我想在数据不变的情况下自己换一套样式

代码示例:

const plugin = five.plugins.panoTagPlugin

/** 自定义文本标签组件 */
function BeautifulTextTag(props: { tag: TagInstance }) {
  return <div>I'm a beautiful text tag, my title is {props.tag.data.title}</div>
}

plugin.registerRenderer('Text', (container: HTMLDivElement, tagInstance: TagInstance) => {
  // 使用 <BeautifulTextTag> 来渲染 contentType 为 "Text" 的标签,替换掉默认的渲染方式
  ReactDOM.render(<BeautifulTextTag tag={tag}></BeautifulTextTag>, container)
  // 需返回销毁函数
  return () => ReactDOM.unmountComponentAtNode(container)
})

// 原有标签数据
plugin.load({
  tagList: [
    {
      contentType: 'Text',
      position: [0, 1, 2],
      data: {
        title: 'text tag',
      },
    },
  ],
})

3. 使用内置的渲染器来渲染自定义标签

使用场景:

我有一套 contentType 为 My_Text 的标签数据, 我觉得PanoTagPlugin里的文本标签样式正好符合我的需求,我想用插件内置的文本标签来渲染我这套数据,并且我无法改动原始数据类型。

代码示例:

const plugin = five.plugins.panoTagPlugin

/** 使用内置的 `Text` 标签的样式来渲染 `My_Text`标签 */
plugin.bindRenderer('My_Text', `Text`)

// 原有标签数据
plugin.load({
  tagList: [
    {
      contentType: 'My_Text',
      position: [0, 1, 2],
      data: {
        title: 'text tag',
      },
    },
  ],
})

4. 标签data更改时,组件数据同步更新

使用场景: 我有一个输入框,我输入的内容要实时渲染到我的自定义文本标签上

代码示例:

// 示例2中的自定义标签组件 BeautifulTextTag
function BeautifulTextTag(props: { tag: TagInstance }) {
  const [data, setData] = useState(props.tag.data)

  React.useEffect(() => {
    // 监听 dataChanged,实时更新 state
    props.tag.hooks.on('dataChanged', setData)

    return () => props.tag.hooks.off('dataChanged', setData)
  }, [props.tag])


  return <div>inputting text is: {data.title}</div>
}

// input 输入框
function Input() {

  const setText = (inputValue: string) => {
    plugin.changeDataById('id', { title: inputValue })
  }

  return <input value={text} onChange={(e) => setText(e.target.value)} />
}

监听标签事件

支持的事件

  • click 单个标签点击事件
  • playStateChange 标签内多媒体播放状态变化事件
  • exposure 标签曝光事件
  • show 标签插件显示事件
  • hide 标签插件隐藏事件
  • enable 标签插件启用事件
  • disable 标签插件禁用事件

参考一下代码进行事件监听:

// 监听标签点击事件
pluginInstance.hooks.on("click", (params: { event: Event } & (
      | { target: 'TagPoint'; tag: PointTagInstance }
      | { target: 'TagContent'; tag: TagInstance }
      | { target: 'AudioTagPlayIcon'; tag: TagInstance; audioInstance: HTMLAudioElement }
      | { target: 'TagModel'; tag: TagInstance<'Model', 'Model'> | TagInstance<'MediaModel', 'Model'> }
) => {
  console.log("click", params);
});

// 监听标签内多媒体播放状态变化事件
pluginInstance.hooks.on("playStateChange", (params: { event: Event; state: 'playing' | 'paused'; tag: TagInstance; mediaInstance: HTMLMediaElement }) => {
  console.log("playStateChange", params);
});

// 监听标签曝光事件
pluginInstance.hooks.on("exposure", (params: { id: TagId; type: 'start' | 'end' }) => {
  console.log("exposure", params);
});

// 监听标签插件显示事件
pluginInstance.hooks.on("show", (options: { userAction: boolean; withAnimation: boolean }) => {
  console.log("show", options);
});

// 监听标签插件隐藏事件
pluginInstance.hooks.on("hide", (options: { userAction: boolean; withAnimation: boolean }) => {
  console.log("hide", options);
});

// 监听标签插件启用事件
pluginInstance.hooks.on("enable", (options: { userAction: boolean }) => {
  console.log("enable", options);
});

// 监听标签插件禁用事件
pluginInstance.hooks.on("disable", (options: { userAction: boolean }) => {
  console.log("disable", options);
});

移除事件监听

// 移除事件监听
pluginInstance.hooks.off('click', clickHandler)
pluginInstance.hooks.off('playStateChange', playStateChangeHandler)
pluginInstance.hooks.off('exposure', exposureHandler)
pluginInstance.hooks.off('show', showHandler)
pluginInstance.hooks.off('hide', hideHandler)
pluginInstance.hooks.off('enable', enableHandler)
pluginInstance.hooks.off('disable', disableHandler)

数据结构

插件中最重要的一个结构是 Tag,添加热点标签,修改标签信息等操作都需要使用,其对应的数据结构如下:

export type Tag<
  C extends ContentType = any,
  P extends PointType = any,
  D extends DimensionType = any,
  CustomDataType extends Object = {},
> = {
  /** 开启/禁用 */
  enabled?: boolean
  /** 唯一标识 */
  id: TagId
  /** 一个点的标签/4个点的标签 */
  pointType: P
  /** 2维/3维类型 */
  dimensionType: D
  /** 内容类型,根据内容类型展示对应UI */
  contentType: C
  /** 点 */
  position: P extends PointType.PointTag
    ? Position
    : P extends PointType.PlaneTag
    ? [Position, Position, Position, Position]
    : any
  /** 自定义标签内容 */
  element?: string | Element | ElementRenderer
  /** 标签数据 */
  data: C extends ContentType.Custom ? CustomDataType : ContentTypeMap[C]
  /** 「展开/收起」 「可见/不可见」 的策略配置 */
  config?: TagConfig<C, P, D, CustomDataType>
  /** 法向量 */
  normal?: Position
  /** 样式 */
  style?: {
    /** 小圆点样式 */
    point?: { style: 'Default' } | { style: 'CustomIcon'; iconUrl: string } | { style: 'noPoint' }
    /** 收起的时候的动画延时,单位:ms */
    foldedPointDelay?: number
  }
} & (D extends DimensionType.Three
  ? P extends PointType.PointTag
    ? { normal: Position }
    : unknown
  : unknown) /** 三维标签需要法向量 */

标签类型定义

export enum ContentType {
  /** 音频标签 */
  Audio = 'Audio',
  /** 文本标签 */
  Text = 'Text',
  /** 图文标签 */
  ImageText = 'ImageText',
  /** VR跳转标签 */
  Link = 'Link',
  /** 营销标签 */
  Marketing = 'Marketing',
  /** 图片视频贴片 */
  MediaPlane = 'MediaPlane',
  /** 其他/自定义标签 */
  Custom = 'Custom',
}

标签维度类型定义

export enum DimensionType {
  Two = '2D',
  Three = '3D',
}

标签点位类型定义

export enum PointType {
  PointTag = 'PointTag',
  PlaneTag = 'PlaneTag',
}

demo 源码参考

demo 源码参考