全景热点标签
PanoTagPlugin
功能说明
全景热点标签插件 提供在全景模式下标注房源不同位置热点标签信息。
详细功能点如下:
- 热点标签共分为“音频标签(Audio)”、“文本标签(Text)”、“图文标签(ImageText)”、“VR 跳转标签(Link)”、“营销标签(Marketing)”、“图片视频贴片(MediaPlane)”、“自定义标签(Custom)”等。
- 标签按照维度类型(DimensionType)可以分为:“2D(Two)”和“3D(Three)”两种。
- 按照点位类型(PointType)来分,标签又可以分为:点标记(PointTag)和平面标记(PlaneTag)两种。
- 用户可以自由组合上述标签分类属性,根据自己的业务类型,创造更加适合的全景热点标签。
示例效果
安装引用
请按需选择 yarn 或 npm 安装方式:
- npm
- Yarn
- pnpm
- Bun
npm install @realsee/dnalogelyarn add @realsee/dnalogelpnpm add @realsee/dnalogelbun add @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>载入数据
// 调用 `load` 方法载入全景标签数据
const plugin: PanoTagPluginControllerplugin.PanoTagPluginController.load(data: Tags): Promise<void>load(const data: {
tagList: Tag[];
}
data)
核心方法
载入插件数据
全量加载数据(会覆盖之前的数据)
const plugin: PanoTagPluginControllerplugin.PanoTagPluginController.load(data: Tags): Promise<void>load(const data: {
tagList: Tag[];
}
data)增量添加数据
const plugin: PanoTagPluginControllerplugin.PanoTagPluginController.addTag(tag: Tag | Tag[], options?: {
updateMaskUrl?: boolean;
}): Promise<void>
addTag(let tag: anytag)清除标签数据
const plugin: PanoTagPluginControllerplugin.PanoTagPluginController.clearTags(): voidclearTags()修改标签配置
const plugin: PanoTagPluginControllerplugin.PanoTagPluginController.changeConfig(config: Pick<Tags, "globalConfig" | "contentTypeConfig">, merge?: boolean): voidchangeConfig({ globalConfig?: TagConfig<"Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown"> | undefined全局配置
globalConfig, contentTypeConfig?: {
[x: string & Record<never, never>]: TagConfig<any> | 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;
... 1560 more ...;
"[ModelLike]-Any-Audio-plane"?: TagConfig<...> | undefined;
} | undefined
按type配置
contentTypeConfig })
const plugin: PanoTagPluginControllerplugin.PanoTagPluginController.changeGlobalConfig(globalConfig: Tags["globalConfig"], merge?: boolean): voidchangeGlobalConfig(let globalConfig: anyglobalConfig)
const plugin: PanoTagPluginControllerplugin.PanoTagPluginController.changeContentTypeConfig<any>(key: any, contentTypeConfig: TagConfig<any> | TagConfig<any>, merge?: boolean): voidchangeContentTypeConfig(let contentTypeKey: anycontentTypeKey, let config: anyconfig)启用/禁用/展示/隐藏/销毁
// 开启插件
const plugin: PanoTagPluginControllerplugin.PanoTagPluginController.enable(params?: {
userAction?: boolean;
}): void
启用插件,让插件能够响应交互
enable()
// 禁用插件
const plugin: PanoTagPluginControllerplugin.PanoTagPluginController.disable(params?: {
userAction?: boolean;
}): void
禁用插件,让插件停止响应交互
disable()
// 显示插件
const plugin: PanoTagPluginControllerplugin.PanoTagPluginController.show(params?: {
userAction?: boolean;
}): Promise<void>
展示UI
show()
// 隐藏插件
const plugin: PanoTagPluginControllerplugin.PanoTagPluginController.hide(params?: {
userAction?: boolean;
}): Promise<void>
隐藏UI
hide()
// 销毁插件
const plugin: PanoTagPluginControllerplugin.PanoTagPluginController.dispose(): void插件进行销毁
dispose()修改标签数据
修改 tag.data
const plugin: PanoTagPluginControllerplugin.PanoTagPluginController.changeDataById<"Model" | "Text" | "Audio" | "ImageText" | "Image" | "Video" | "Panorama" | "Link" | "VRLink" | "Sticker" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Custom" | "Unknown">(id: TagId, data: PartialObjectDeep<({
modelUrl: string;
} & {
extraData?: Object;
} & {
[key: string]: any;
}) | ({
appearance?: "line" | "plane";
text?: string;
title?: string;
description?: string;
descriptionMaxRows?: number | null;
titleMaxRows?: number | null;
edit?: {
title?: {
inputting?: boolean;
placeholder?: string;
};
description?: {
inputting?: boolean;
placeholder?: string;
};
};
} & {
extraData?: Object;
} & {
[key: string]: any;
}) | ... 6 more ... | (Object & ... 1 more ... & {
[key: string]: any;
})>, deepMerge?: boolean): void
changeDataById(1, let data: anydata)
// or
const const tag: BaseTag<"Model" | "Text" | "Audio" | "ImageText" | "Image" | "Video" | "Panorama" | "Link" | "VRLink" | "Sticker" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Custom" | "Unknown", StickType> | undefinedtag = const plugin: PanoTagPluginControllerplugin.TagUtil.getTagById<"Model" | "Text" | "Audio" | "ImageText" | "Image" | "Video" | "Panorama" | "Link" | "VRLink" | "Sticker" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Custom" | "Unknown", StickType>(id: TagId): BaseTag<"Model" | "Text" | "Audio" | "ImageText" | "Image" | "Video" | "Panorama" | "Link" | "VRLink" | "Sticker" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Custom" | "Unknown", StickType> | undefinedgetTagById(1)
const tag: BaseTag<"Model" | "Text" | "Audio" | "ImageText" | "Image" | "Video" | "Panorama" | "Link" | "VRLink" | "Sticker" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Custom" | "Unknown", StickType> | undefinedtag?.BaseTag<"Model" | "Text" | "Audio" | "ImageText" | "Image" | "Video" | "Panorama" | "Link" | "VRLink" | "Sticker" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Custom" | "Unknown", StickType>.changeData(data: PartialObjectDeep<({
modelUrl: string;
} & {
extraData?: Object;
} & {
[key: string]: any;
}) | ({
appearance?: "line" | "plane";
text?: string;
title?: string;
description?: string;
descriptionMaxRows?: number | null;
titleMaxRows?: number | null;
edit?: {
title?: {
inputting?: boolean;
placeholder?: string;
};
description?: {
inputting?: boolean;
placeholder?: string;
};
};
} & {
extraData?: Object;
} & {
[key: string]: any;
}) | ... 6 more ... | (Object & ... 1 more ... & {
[key: string]: any;
})>, deepMerge?: boolean): void
changeData(let data: anydata)-
destroyTagById: (id: TagId | TagId[]) => void销毁 tag -
pauseCurrentMedia: () => void暂停当前标签内进行的所有多媒体
热点标签配置
热点标签可以通过丰富的配置来实现各种不同的标签展示效果:
添加 config
- 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 TagContentType = "Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "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 TagContentType = "Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown">TagConfig = {}
const pluginInstance: PanoTagPluginControllerpluginInstance.PanoTagPluginController.load(data: Tags): Promise<void>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: string & Record<never, never>]: TagConfig<any> | 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;
... 1560 more ...;
"[ModelLike]-Any-Audio-plane"?: TagConfig<...> | undefined;
} | undefined
按type配置
contentTypeConfig: {
'Text': const TextTagConfig: TagConfig<"Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown">TextTagConfig
}
})- 实时切换
import { interface TagConfig<C extends TagContentType = "Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "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 TagContentType = "Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown">TagConfig = {}
const const contentTypeConfig: {
Text: {};
}
contentTypeConfig = {
'Text': {}
}
const pluginInstance: PanoTagPluginControllerpluginInstance.PanoTagPluginController.changeConfig(config: Pick<Tags, "globalConfig" | "contentTypeConfig">, merge?: boolean): voidchangeConfig({
globalConfig?: TagConfig<"Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown"> | undefined全局配置
globalConfig,
contentTypeConfig?: {
[x: string & Record<never, never>]: TagConfig<any> | 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;
... 1560 more ...;
"[ModelLike]-Any-Audio-plane"?: TagConfig<...> | undefined;
} | undefined
按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 TagContentType = "Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown">TagConfig = {}
const pluginInstance: PanoTagPluginControllerpluginInstance.PanoTagPluginController.load(data: Tags): Promise<void>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 TagContentType = "Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown">TagConfig = {}
const pluginInstance: PanoTagPluginControllerpluginInstance.PanoTagPluginController.load(data: Tags): Promise<void>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: string & Record<never, never>]: TagConfig<any> | 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;
... 1560 more ...;
"[ModelLike]-Any-Audio-plane"?: TagConfig<...> | undefined;
} | undefined
按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 TagContentType = "Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown">TagConfig = {}
const pluginInstance: PanoTagPluginControllerpluginInstance.PanoTagPluginController.load(data: Tags): Promise<void>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;
visibleFiveMode?: TagVisibleMode | ((tag: TagInstance) => TagVisibleMode);
entryFromModel?: boolean;
followModelVisibility?: boolean;
alwaysShowWhenMovePano?: boolean;
visibleDistance?: MinMax | "unLimited";
visiblePanoIndex?: "all" | "current" | number[];
intersectRaycaster?: boolean | {
enabled?: boolean;
distanceAccuracy?: number;
checkPoints?: "center" | "corner" | Vector3[];
needPassed?: number;
};
angleRange?: MinMax;
} | ConfigFunction) | undefined;
... 11 more ...;
__experimental_PolygonMeshConfig?: {
enabled?: boolean;
} | undefined;
}
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.
Simplify<interface TagConfig<C extends TagContentType = "Audio" | "Text" | "ImageText" | "Image" | "Video" | "Link" | "Sticker" | "VRLink" | "PanoLink" | "Marketing" | "MediaPlane" | "MediaModel" | "Model" | "Panorama" | "Custom" | "Unknown">TagConfig> = {
visibleConfig?: {
keep?: "visible" | "hidden" | null;
visibleFiveMode?: TagVisibleMode | ((tag: TagInstance) => TagVisibleMode);
entryFromModel?: boolean;
followModelVisibility?: boolean;
alwaysShowWhenMovePano?: boolean;
visibleDistance?: MinMax | "unLimited";
visiblePanoIndex?: "all" | "current" | number[];
intersectRaycaster?: boolean | {
enabled?: boolean;
distanceAccuracy?: number;
checkPoints?: "center" | "corner" | Vector3[];
needPassed?: number;
};
angleRange?: MinMax;
} | ConfigFunction | undefined
visibleConfig: {
keep?: "visible" | "hidden" | null | undefinedkeep: null,
visibleFiveMode?: TagVisibleMode | ((tag: TagInstance) => TagVisibleMode) | undefinedvisibleFiveMode: ['Panorama'],
visibleDistance?: MinMax | "unLimited" | undefinedvisibleDistance: { MinMax.min?: number | undefinedmin: 0, MinMax.max?: number | undefinedmax: 10 },
visiblePanoIndex?: "all" | "current" | number[] | undefinedvisiblePanoIndex: var undefinedundefined,
// 仅在 Mapview/Floorplan 下跟随当前楼层可见性
followModelVisibility?: boolean | undefinedfollowModelVisibility: false,
// 从模型态点击入口进入全景时,是否允许看向标签点(仅全景标签)
entryFromModel?: boolean | undefinedentryFromModel: false,
// 碰撞检测配置:控制遮挡判断
intersectRaycaster?: boolean | {
enabled?: boolean;
distanceAccuracy?: number;
checkPoints?: "center" | "corner" | Vector3[];
needPassed?: number;
} | undefined
intersectRaycaster: {
enabled?: boolean | undefinedenabled: true,
distanceAccuracy?: number | undefineddistanceAccuracy: 0.04,
checkPoints?: "center" | "corner" | Vector3[] | undefinedcheckPoints: 'corner',
needPassed?: number | undefinedneedPassed: 1,
},
// 3D 标签在全景下,根据相机与法线夹角的可见范围
angleRange?: MinMax | undefinedangleRange: var undefinedundefined,
},
unfoldedConfig?: ConfigFunction | {
keep?: "unfolded" | "folded" | null;
autoFoldWhenHide?: false;
disableUnfold?: true;
disableFold?: true;
unfoldDistance?: MinMax;
autoUnfold?: false | {
enable?: boolean;
strategy?: "ScreenPostion";
autoUnfoldProjectX?: MinMax;
} | {
enable?: boolean;
strategy?: "MinimumDistance";
maxNumber?: number;
distance?: MinMax;
} | {
enable?: boolean;
strategy?: "FoldWhenMove";
} | {
enable?: boolean;
strategy?: "ScreenCenter";
distance?: MinMax;
maxNumber?: number;
};
} | undefined
unfoldedConfig: {
keep?: "unfolded" | "folded" | null | undefinedkeep: null,
autoFoldWhenHide?: false | undefinedautoFoldWhenHide: false,
autoUnfold?: false | {
enable?: boolean;
strategy?: "ScreenPostion";
autoUnfoldProjectX?: MinMax;
} | {
enable?: boolean;
strategy?: "MinimumDistance";
maxNumber?: number;
distance?: MinMax;
} | {
enable?: boolean;
strategy?: "FoldWhenMove";
} | {
enable?: boolean;
strategy?: "ScreenCenter";
distance?: MinMax;
maxNumber?: number;
} | undefined
autoUnfold: {
enable?: boolean | undefinedenable: true,
strategy?: "MinimumDistance" | undefined自动展开:最近标签自动展开
strategy: 'MinimumDistance',
maxNumber?: number | undefinedmaxNumber: 1,
distance?: MinMax | undefineddistance: { MinMax.min?: number | undefinedmin: 0, MinMax.max?: number | undefinedmax: 10 }
},
// 进入可见范围则展开,离开则收起
unfoldDistance?: MinMax | undefinedmin-max米内自动展开,否则收起
unfoldDistance: var undefinedundefined,
// 其他策略:'ScreenPostion' | 'FoldWhenMove' | 'ScreenCenter'
// ScreenPostion: 根据投影 x 位置范围展开
// FoldWhenMove: 屏幕移动自动收起
// ScreenCenter: 屏幕中心优先展开
},
// 初始状态
initialState?: {
visible?: boolean;
unfolded?: boolean;
} | undefined
初始状态
initialState: {
visible?: boolean | undefinedvisible: var undefinedundefined,
unfolded?: boolean | undefinedunfolded: var undefinedundefined,
},
// 初始化合并到 tag.data 的默认数据(支持 important 覆盖)
initialData?: (PartialObjectDeep<({
text?: string;
title?: string;
appearance?: "line" | "plane";
theme?: "dark" | "light";
audioUrl: string;
} & {
extraData?: Object;
} & {
[key: string]: any;
}) | ({
appearance?: "line" | "plane";
text?: string;
title?: string;
description?: string;
descriptionMaxRows?: number | null;
titleMaxRows?: number | null;
edit?: {
title?: {
inputting?: boolean;
placeholder?: string;
};
description?: {
inputting?: boolean;
placeholder?: string;
};
};
} & {
extraData?: Object;
} & {
[key: string]: any;
}) | ... 6 more ... | (Object & ... 1 more ... & {
[key: string]: any;
})> & {
...;
}) | undefined
initialData: {
important?: boolean | undefinedimportant: false,
},
// 3D 标签容器配置
tag3DConfig?: ({
ratio?: number;
dpr?: number;
devicePixelRatio?: number;
autoRender?: boolean;
container?: HTMLElement;
pointerEvents?: "none" | "auto";
wrapperStyle?: Partial<CSSStyleDeclaration>;
} & ({
mode?: "front";
} | {
mode: "behind";
behindFiveContainer?: HTMLElement;
})) | undefined
tag3DConfig: {
// ratio 等缩放参数在全局默认里也可配置
}
}修改Config
参照 修改标签配置
常用配置项说明
visibleConfig(可见性)
keep: 在指定模式下强制可见/不可见。可选'visible' | 'hidden' | null。设置后同组内其他项不再生效。visibleFiveMode: 指定在哪些 Five 模式下可见。支持Five.Mode | Five.Mode[] | 'PanoramaLike'|'ModelLike'|'all' | ((tag: TagInstance) => TagVisibleMode)返回。followModelVisibility: Mapview/Floorplan 下是否仅在当前楼层可见。entryFromModel: 模型态点击入口,进入全景后自动看向标签(仅全景标签)。visibleDistance: 按与摄像机距离控制可见范围,MinMax或'unLimited'。visiblePanoIndex: 控制在哪些全景点位可见:'all' | 'current' | number[]。intersectRaycaster: 标签遮挡检测(开启将按模型遮挡判断可见)。enabled: 是否开启distanceAccuracy: 距离误差checkPoints:'center' | 'corner' | Vector3[]needPassed: 通过多少检测点算可见
angleRange: 全景下(3D 标签)按相机与法线夹角范围可见。
unfoldedConfig(展开/收起)
keep:'unfolded' | 'folded' | null(优先级最高)。autoFoldWhenHide: 隐藏时自动收起(部分标签天然不可收起)。unfoldDistance: 进入范围展开,出范围收起。autoUnfold策略:'MinimumDistance': 最近标签自动展开,支持maxNumber、distance。'ScreenPostion': 根据标签投影到屏幕的 X 坐标范围展开(autoUnfoldProjectX,取值区间为 [-1, 1],越接近 0 越靠近屏幕中心)。'FoldWhenMove': 屏幕移动时收起。'ScreenCenter': 屏幕中心优先展开(支持distance、maxNumber)。
初始行为与数据
initialState: 初始的visible/unfolded。initialData: 合并到tag.data的默认数据,支持important强覆盖。
渲染与交互
tag3DConfig: 3D 容器参数(全局默认里也常配置 ratio 等)。modelConfig.autoLookAtEnabled: 模型标签进入全景后是否自动朝向相机。renderType:'Mesh' | 'Dom'(营销贴片等在特定条件可用 Mesh)。simulate3D: 是否模拟近大远小。clickable: 标签是否可点。
popoverConfig(浮窗,开启后 unfoldedConfig 失效)
enabled: 是否启用浮窗(默认false)。trigger:'hover' | 'click'(默认'hover')。triggerDelay: 悬停触发延迟(单位 ms,插件默认 500,可覆盖)。placement: 浮窗位置(top/bottom/left/right/.../auto)。autoPlacementBaseSpace: 自动定位时的参考分区{ top, right, bottom }。transitionDuration: 动画时长(ms)。theme:'dark' | 'light'。toolbar:{ showMore?: boolean; showShare?: boolean }。zIndex: 浮层 zIndex(默认2000000)。imageURLTransform:(url, { width, height }) => string图片地址加工。viewMoreText: “查看更多” 文案。beforeOpen(tag): 返回false可拦截 hover 打开。
示例:通过 load 全局开启浮窗并使用 hover 触发
pluginInstance.load({
tagList: [],
globalConfig: {
popoverConfig: {
enabled: true,
trigger: 'hover',
triggerDelay: 500,
placement: 'auto',
},
},
})示例:给单个标签开启浮窗,并自定义主题/工具栏
pluginInstance.load({
tagList: [
{
// ... 其他 tag 字段
config: {
popoverConfig: {
enabled: true,
theme: 'light',
toolbar: { showMore: true, showShare: true },
},
},
},
],
})示例:运行时更新全局配置
pluginInstance.changeGlobalConfig({
popoverConfig: {
enabled: true,
trigger: 'click',
},
})添加自定义热点标签
热点标签中有一个标签类型叫做“自定义热点标签”,使用这个标签类型,开发者可以根据自己的业务需要,自定义添加任意符合规范的标签样式。
可以参考下面的例子:
// 添加自定义热点
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标签插件禁用事件hover标签悬停触发事件(开启 popover 且 trigger='hover' 或 'click' 时触发)showPopover标签浮窗显示事件hidePopover标签浮窗隐藏事件
参考一下代码进行事件监听:
// 监听标签点击事件
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);
});支持 hover 触发
开启浮窗并使用 hover/点击触发展示:
// 全局开启浮窗,并配置触发方式/延迟
pluginInstance.changeGlobalConfig({
popoverConfig: {
enabled: true,
trigger: 'hover', // 或 'click'
triggerDelay: 500, // 仅在 trigger='hover' 时生效
},
})
// 也可在 load 时配置,或在标签级 config.popoverConfig 中覆盖监听 hover 与浮窗事件:
// 监听标签 hover(悬停或点击触发展示前的统一事件)
pluginInstance.hooks.on('hover', ({ event, tag }) => {
console.log('hover', { event, id: tag.id, contentType: tag.contentType })
})
// 监听浮窗显示/隐藏
tagInstance.hooks.on('showPopover', () => console.log('popover show'))
tagInstance.hooks.on('hidePopover', () => console.log('popover hide'))运行时临时禁用/启用 hover:
// 全局关闭/开启 hover(不影响配置,只做临时屏蔽)
pluginInstance.setGlobalHoverEnabled(false)
pluginInstance.setGlobalHoverEnabled(true)
// 按标签 ID 禁用/启用 hover(仅临时控制)
pluginInstance.setTagHoverEnabled('tag-id', false)
pluginInstance.setTagHoverEnabled('tag-id', true)移除事件监听
// 移除事件监听
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',
}