上图
1: 创建一个热点基类
export default class Hot {
/**
* @description id
* @type {string}
*/
id = ''
/**
* 热点绑定的网格id
* @type {number}
*/
modelId = 0
/**
* 热点绑定的元素
* @type {string}
*/
innerHTML = ''
/**
* 元素
* @type {HTMLElement}
*/
element = null
/**
* 视角点
* @type {{position: {x: number, y: number, z: number}, orientation: {}}}
*/
viewPoint = {
position: {},
orientation: {}
}
/**
* 热点位置
* @type {{x: number, y: number, z: number}}
*/
position = null
/**
* 热点被点击触发
* @type{Function}
*/
click = function () {
}
/**
* 更新时调用
* @type{Function}
*/
onUpdate = function (e) {
}
/**
* 热点屏幕坐标
* @type {{x: number, y: number}}
*/
screenCoordinates = {}
/**
* 设置关联dom
* @type {HTMLElement}
*/
connectionDom = null
/**
* 设置svg线 dom
* @type {HTMLElement}
*/
linesDom = null
/**
* 偏移多少个像素
* @type {{x: number, y: number}}
*/
offset = {
x: 0,
y: 0
}
/**
* 关联线样式
* @type {{
* stroke: string,
* strokeWidth: number,
* opacity: number
* }}
*/
connectionLineStyle = {
stroke: 'red',
strokeWidth: 3,
opacity: 0.7
}
/**
* 创建一个热点
* @type{{id: string,
* modelId: number,
* innerHTML: string,
* viewPoint: {position: {},
* orientation: {}},
* position: {x: number, y: number, z: number},
* click: Function,
* onUpdate: Function,
* offset: {x: number, y: number}
* }}
*/
constructor (options) {
this.id = options.id
this.modelId = options.modelId
this.innerHTML = options.innerHTML
this.viewPoint = options.viewPoint
this.position = options.position
this.click = options.click
this.onUpdate = options.onUpdate
this.offset = options.offset || this.offset
}
}
2: 创建热点管理对象
import EventConstant from '../constant/EventConstant'
import CesiumUtils from '../utils/CesiumUtils'
import GraphUtils from '../utils/GraphUtils'
export default class HotManager {
/**
* 视图管理
* @type {Viewer}
*/
viewer = null
/**
* 热点列表
* @type {Hot[]}
*/
hotList = []
/**
* 构建一个热点管理工具
* @param viewer
*/
constructor (viewer) {
this.viewer = viewer
this.viewer.eventManager.addEventListener(EventConstant.RENDER, e => {
try{
this.hotList.forEach(item => {
const windowCoordinates = CesiumUtils.wgs84ToWindowCoordinates(this.viewer.baseViewer.scene, item.position)
if (windowCoordinates) {
this.onUpdate(item, windowCoordinates)
// 如果关联dom 有值 且位置有变更, 需要更新连接线线的位置
if (item.connectionDom && (windowCoordinates.x !== item.screenCoordinates.x || windowCoordinates.y !== item.screenCoordinates.y)) {
this._processConnectionLines(item)
}
item.screenCoordinates = windowCoordinates
}
})
}catch(e) {
console.error(e, ' 请检查传入的参数[position]是否错误')
}
})
}
/**
* 新增一个热点
* @param hot{Hot} 热点
*/
add (hot) {
if (!hot) {
throw new Error('错误的热点对象')
}
const find = this.hotList.find(item => item.id === hot.id)
if (find) {
throw new Error('不允许重复的id!')
}
this.hotList.push(hot)
let element = document.createElement('div')
if (hot.innerHTML.constructor === HTMLDivElement){
element = hot.innerHTML
} else {
element.innerHTML = hot.innerHTML
}
element.style.position = 'absolute'
element.style.top = '0px'
element.style.left = '0px'
// 窗口内挂上dom
this.viewer.baseViewer.container.appendChild(element)
element.style.display = 'block'
element.onclick = () => {
this.click(hot)
}
hot.element = element
}
/**
* 实时更新点位 *** 需要变更时外部重写 ***
* @param hot{Hot}
* @param screenPosition{{x: number, y: number}}
*/
onUpdate (hot, screenPosition) {
hot.element.style.left = screenPosition.x - hot.offset.x + 'px'
hot.element.style.top = screenPosition.y - hot.offset.y + 'px'
}
/**
* 热点信息
* @param hot{Hot}
*/
click (hot) {
hot.viewPoint && this.viewer.flyTo(hot.viewPoint.position, hot.viewPoint.orientation)
}
/**
* 飞到这个热点
* @param hot{Hot} 热点
*/
flyToHot(hot) {
this.viewer.flyTo(hot.viewPoint.position, hot.viewPoint.orientation)
}
/**
* 移除全部热点
*/
removeAll () {
const length = this.hotList.length
for (let i = 0; i < length; i++) {
this.remove(this.hotList[0])
}
}
/**
* 移除热点
* @param hot{Hot} 热点
*/
remove (hot) {
this.viewer.baseViewer.container.removeChild(hot.element)
hot.linesDom && this.viewer.baseViewer.container.removeChild(hot.linesDom)
hot.connectionDom = null
hot.linesDom = null
const index = this.hotList.findIndex(item => item === hot)
this.hotList.splice(index, 1)
}
/**
* 通过id移除指定热点
* @param id{string}
*/
removeById (id) {
const hot = this.hotList.find(item => item.id === id)
hot && this.remove(hot)
}
/**
* 设置连接dom
* @param hot{Hot} 指定热点
* @param dom{HTMLElement} HTML dom元素
* @param lineStyle{{
* stroke: string,
* strokeWidth: number,
* opacity: number
* }} 连接线样式
*/
setConnectionDom (hot, dom, lineStyle) {
const element = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
element.style.height = '100%'
element.style.width = '100%'
element.style.position = 'absolute'
element.style.top = '0px'
element.style.pointerEvents = 'none'
element.id = hot.id + '_hot'
hot.connectionDom = dom
// 覆盖默认样式
hot.connectionLineStyle = { ...hot.connectionLineStyle, ...lineStyle }
hot.linesDom = element
// 窗口内挂上dom
this.viewer.baseViewer.container.appendChild(element)
}
/**
* 处理连接线渲染
* @param hot{Hot} 热点
* @private
*/
_processConnectionLines (hot) {
const lines = GraphUtils.generateLine(hot.connectionDom, hot.element)
let innerHTML = ''
lines.forEach(ite => {
innerHTML += '<line x1="' + ite.start.x + '"' +
' x2="' + ite.end.x + '" ' +
'y1="' + ite.start.y + '"' +
' y2="' + ite.end.y + '"' +
'style="stroke:' + hot.connectionLineStyle.stroke +
';stroke-width:' + hot.connectionLineStyle.strokeWidth +
'; opacity: ' + hot.connectionLineStyle.opacity + ';"> </line>'
})
hot.linesDom.innerHTML = innerHTML
}
}
3: 一个简单的dom连接计算
/**
* 生成连接线点位
* @param startObj{HTMLElement}
* @param endObj{HTMLElement}
* @return {[]}
*/
static generateLine (startObj, endObj) {
if (!startObj.getBoundingClientRect) return []
let end = {}
const startRange = startObj.getBoundingClientRect()
const endRange = endObj.getBoundingClientRect()
const distance = 0.05
const arr = []
const start = {}
start.x = Number(startRange.x)
start.y = Number(startRange.y) + startRange.height / 2
end.x = Number(endRange.x) + endRange.width / 2
end.y = Number(endRange.y) + endRange.height
if (end.x > window.innerWidth || end.y > window.innerHeight) {
return []
}
// 第1个点
arr.push({
start: {
...start
},
end: {
x: start.x - start.x * distance,
y: start.y
}
})
/* // 第2个点. 下
arr.push({
start: {
...arr[0].end
},
end: {
x: arr[0].end.x,
y: end.y - start.y + ((end.y - start.y) * distance) + start.y
}
}) */
/* // 第3个点. 下
arr.push({
start: {
...arr[1].end
},
end: {
x: end.x,
y: arr[1].end.y
}
}) */
// 第3个点. 下
arr.push({
start: {
...arr[0].end
},
end: {
...end
}
})
return arr
}
4:使用 (这里也可以用vue v-show 改成false, 这样节点生成了, 可以使用双向绑定, 非常滴方便 )
const hot = new Hot({
id: Clever.MathUtils.generateUUID(),
position: res.position,
viewPoint: this.viewer.getViewPoint(),
offset: {x: 70, y: 141},
innerHTML: document.getElementById('hot_point').innerHTML
})
// 热点管理
this.hotManager = new HotManager(this.viewer)
this.hotManager.add(hot)
this.hotManager.setConnectionDom(hot, document.getElementById('novice')) // 设置连接的dom
5: 附赠css游动样式一个
@keyframes dong {
0% {
transform: translate(0px, 0px);
}
50% {
transform: translate(0px, -10px);
}
100% {
transform: translate(0px, 0px);
}
}
.top{
animation: dong 1s infinite;
}