Feat:新增性能模式

This commit is contained in:
街角小林
2024-07-25 16:40:47 +08:00
parent 4e7d59b328
commit 2dee415a64
7 changed files with 135 additions and 81 deletions

View File

@@ -194,7 +194,7 @@ class MindMap {
this.renderer.reRender = true // 标记为重新渲染
this.renderer.clearCache() // 清空节点缓存池
this.clearDraw() // 清空画布
this.render(callback, (source = ''))
this.render(callback, source)
}
// 获取或更新容器尺寸位置信息
@@ -288,7 +288,9 @@ class MindMap {
// 更新配置
updateConfig(opt = {}) {
this.emit('before_update_config', this.opt)
this.opt = this.handleOpt(merge.all([defaultOpt, this.opt, opt]))
this.emit('after_update_config', this.opt)
}
// 获取当前布局结构
@@ -379,6 +381,9 @@ class MindMap {
// 导出
async export(...args) {
try {
if (!this.doExport) {
throw new Error('请注册Export插件')
}
let result = await this.doExport.export(...args)
return result
} catch (error) {
@@ -416,6 +421,11 @@ class MindMap {
addContentToFooter,
node
} = {}) {
const { watermarkConfig, openPerformance } = this.opt
// 如果开启了性能模式,那么需要先渲染所有节点
if (openPerformance) {
this.renderer.forceLoadNode(node)
}
const { cssTextList, header, headerHeight, footer, footerHeight } =
handleGetSvgDataExtraContent({
addContentToHeader,
@@ -459,7 +469,7 @@ class MindMap {
if (!ignoreWatermark && hasWatermark) {
this.watermark.isInExport = true
// 是否是仅导出时需要水印
const { onlyExport } = this.opt.watermarkConfig
const { onlyExport } = watermarkConfig
// 是否需要重新绘制水印
const needReDrawWatermark =
rect.width > origWidth || rect.height > origHeight

View File

@@ -228,6 +228,14 @@ export const defaultOpt = {
// 自定义超链接的跳转
// 如果不传默认会以新窗口的方式打开超链接可以传递一个函数函数接收两个参数link超链接的url、node所属节点实例只要传递了函数就会阻止默认的跳转
customHyperlinkJump: null,
// 是否开启性能模式默认情况下所有节点都会直接渲染无论是否处于画布可视区域这样当节点数量比较多时1000+会比较卡如果你的数据量比较大那么可以通过该配置开启性能模式即只渲染画布可视区域内的节点超出的节点不渲染这样会大幅提高渲染速度当然同时也会带来一些其他问题比如1.当拖动或是缩放画布时会实时计算并渲染未节点的节点所以会带来一定卡顿2.导出图片、svg、pdf时需要先渲染全部节点所以会比较慢3.其他目前未发现的问题
openPerformance: false,
// 性能优化模式配置
performanceConfig: {
time: 250,// 当视图改变后多久刷新一次节点单位ms
padding: 100,// 超出画布四周指定范围内依旧渲染节点
removeNodeWhenOutCanvas: true,// 节点移除画布可视区域后从画布删除
},
// 【Select插件】
// 多选节点时鼠标移动到边缘时的画布移动偏移量

View File

@@ -32,7 +32,8 @@ import {
checkIsNodeStyleDataKey,
removeRichTextStyes,
formatGetNodeGeneralization,
sortNodeList
sortNodeList,
throttle
} from '../../utils'
import { shapeList } from './node/Shape'
import { lineStyleProps } from '../../themes/default'
@@ -144,13 +145,40 @@ class Render {
if (!this.mindMap.opt.enableDblclickBackToRootNode) return
this.setRootNodeCenter()
})
// let timer = null
// this.mindMap.on('view_data_change', () => {
// clearTimeout(timer)
// timer = setTimeout(() => {
// this.render()
// }, 300)
// })
// 性能模式
this.performanceMode()
}
// 性能模式,懒加载节点
performanceMode() {
const { openPerformance, performanceConfig } = this.mindMap.opt
const onViewDataChange = throttle(() => {
if (this.root) this.root.render()
}, performanceConfig.time)
let lastOpen = false
this.mindMap.on('before_update_config', opt => {
lastOpen = opt.openPerformance
})
this.mindMap.on('after_update_config', opt => {
if (opt.openPerformance && !lastOpen) {
// 动态开启性能模式
this.mindMap.on('view_data_change', onViewDataChange)
this.forceLoadNode()
}
if (!opt.openPerformance && lastOpen) {
// 动态关闭性能模式
this.mindMap.off('view_data_change', onViewDataChange)
this.forceLoadNode()
}
})
if (!openPerformance) return
this.mindMap.on('view_data_change', onViewDataChange)
}
// 强制渲染节点,不考虑是否在画布可视区域内
forceLoadNode(node) {
node = node || this.root
if (node) node.render(() => {}, true)
}
// 注册命令
@@ -453,6 +481,7 @@ class Render {
}
this.mindMap.emit('node_tree_render_start')
// 计算布局
this.root = null
this.layout.doLayout(root => {
// 删除本次渲染时不再需要的节点
Object.keys(this.lastNodeCache).forEach(uid => {

View File

@@ -683,7 +683,7 @@ class Node {
}
// 更新节点
update() {
update(forceRender) {
if (!this.group) {
return
}
@@ -710,36 +710,11 @@ class Node {
}
}
// 更新概要
this.renderGeneralization()
this.renderGeneralization(forceRender)
// 更新协同头像
if (this.updateUserListNode) this.updateUserListNode()
// 更新节点位置
let t = this.group.transform()
// // 如果上次不在可视区内,且本次也不在,那么直接返回
// let { left: ox, top: oy } = this.getNodePosInClient(
// t.translateX,
// t.translateY
// )
// let oldIsInClient =
// ox > 0 && oy > 0 && ox < this.mindMap.width && oy < this.mindMap.height
// let { left: nx, top: ny } = this.getNodePosInClient(this.left, this.top)
// let newIsNotInClient =
// nx + this.width < 0 ||
// ny + this.height < 0 ||
// nx > this.mindMap.width ||
// ny > this.mindMap.height
// if (!oldIsInClient && newIsNotInClient) {
// if (!this.isHide) {
// this.isHide = true
// this.group.hide()
// }
// return
// }
// // 如果当前是隐藏状态,那么先显示
// if (this.isHide) {
// this.isHide = false
// this.group.show()
// }
// 如果节点位置没有变化,则返回
if (this.left === t.translateX && this.top === t.translateY) return
this.group.translate(this.left - t.translateX, this.top - t.translateY)
@@ -757,6 +732,17 @@ class Node {
}
}
// 判断节点是否可见
checkIsInClient(padding = 0) {
const { left: nx, top: ny } = this.getNodePosInClient(this.left, this.top)
return (
nx + this.width > 0 - padding &&
ny + this.height > 0 - padding &&
nx < this.mindMap.width + padding &&
ny < this.mindMap.height + padding
)
}
// 重新渲染节点,即重新创建节点内容、计算节点大小、计算节点内容布局、更新展开收起按钮,概要及位置
reRender() {
let sizeChange = this.getSize()
@@ -786,31 +772,41 @@ class Node {
}
// 递归渲染
render(callback = () => {}) {
render(callback = () => {}, forceRender = false) {
// 节点
// 重新渲染连线
this.renderLine()
if (!this.group) {
// 创建组
this.group = new G()
this.group.addClass('smm-node')
this.group.css({
cursor: 'default'
})
this.bindGroupEvent()
this.nodeDraw.add(this.group)
this.layout()
this.update()
} else {
if (!this.nodeDraw.has(this.group)) {
const { openPerformance, performanceConfig } = this.mindMap.opt
// 强制渲染、或没有开启性能模式、或不在画布可视区域内不渲染节点内容
if (
forceRender ||
!openPerformance ||
this.checkIsInClient(performanceConfig.padding)
) {
if (!this.group) {
// 创建组
this.group = new G()
this.group.addClass('smm-node')
this.group.css({
cursor: 'default'
})
this.bindGroupEvent()
this.nodeDraw.add(this.group)
}
if (this.needLayout) {
this.needLayout = false
this.layout()
this.update(forceRender)
} else {
if (!this.nodeDraw.has(this.group)) {
this.nodeDraw.add(this.group)
}
if (this.needLayout) {
this.needLayout = false
this.layout()
}
this.updateExpandBtnPlaceholderRect()
this.update(forceRender)
}
this.updateExpandBtnPlaceholderRect()
this.update()
} else if (openPerformance && performanceConfig.removeNodeWhenOutCanvas) {
this.remove(true)
}
// 子节点
if (
@@ -825,7 +821,7 @@ class Node {
if (index >= this.children.length) {
callback()
}
})
}, forceRender)
})
} else {
callback()
@@ -841,11 +837,13 @@ class Node {
}
// 递归删除,只是从画布删除,节点容器还在,后续还可以重新插回画布
remove() {
remove(keepLine = false) {
if (!this.group) return
this.group.remove()
this.removeGeneralization()
this.removeLine()
if (!keepLine) {
this.removeLine()
}
// 子节点
if (this.children && this.children.length) {
this.children.forEach(item => {
@@ -856,6 +854,10 @@ class Node {
// 销毁节点,不但会从画布删除,而且原节点直接置空,后续无法再插回画布
destroy() {
this.removeLine()
if (this.parent) {
this.parent.removeLine()
}
if (!this.group) return
if (this.emptyUser) {
this.emptyUser()
@@ -863,11 +865,7 @@ class Node {
this.resetWhenDelete()
this.group.remove()
this.removeGeneralization()
this.removeLine()
this.group = null
if (this.parent) {
this.parent.removeLine()
}
this.style.onRemove()
}

View File

@@ -85,7 +85,7 @@ function updateGeneralization() {
}
// 渲染概要节点
function renderGeneralization() {
function renderGeneralization(forceRender) {
if (this.isGeneralization) return
this.updateGeneralizationData()
const list = this.formatGetGeneralization()
@@ -100,7 +100,7 @@ function renderGeneralization() {
this.renderer.layout.renderGeneralization(this._generalizationList)
this._generalizationList.forEach(item => {
this.style.generalizationLine(item.generalizationLine)
item.generalizationNode.render()
item.generalizationNode.render(() => {}, forceRender)
})
}

View File

@@ -297,6 +297,13 @@ class OuterFrame {
if (range[0] === -1 || range[1] === -1) return
const { left, top, width, height } =
getNodeListBoundingRect(nodeList)
if (
!Number.isFinite(left) ||
!Number.isFinite(top) ||
!Number.isFinite(width) ||
!Number.isFinite(height)
)
return
const el = this.createOuterFrameEl(
(left -
outerFramePaddingX -

View File

@@ -1382,22 +1382,24 @@ export const getNodeTreeBoundingRect = (
let minY = Infinity
let maxY = -Infinity
const walk = (root, isRoot) => {
if (!(isRoot && excludeSelf)) {
const { x, y, width, height } = root.group
.findOne('.smm-node-shape')
.rbox()
if (x < minX) {
minX = x
}
if (x + width > maxX) {
maxX = x + width
}
if (y < minY) {
minY = y
}
if (y + height > maxY) {
maxY = y + height
}
if (!(isRoot && excludeSelf) && root.group) {
try {
const { x, y, width, height } = root.group
.findOne('.smm-node-shape')
.rbox()
if (x < minX) {
minX = x
}
if (x + width > maxX) {
maxX = x + width
}
if (y < minY) {
minY = y
}
if (y + height > maxY) {
maxY = y + height
}
} catch (e) {}
}
if (!excludeGeneralization && root._generalizationList.length > 0) {
root._generalizationList.forEach(item => {