Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions simple-mind-map/src/core/render/node/nodeModifyWidth.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ function initDragHandle() {
this.dragHandleMousedownBodyCursor = ''
// 鼠标按下时记录当前节点的left值
this.dragHandleMousedownLeft = 0
// 参考节点宽度列表
this.referenceNodeWidths = []
// 节点宽度与文本宽度的差值
this.widthDiff = 0

this.onDragMousemoveHandle = this.onDragMousemoveHandle.bind(this)
window.addEventListener('mousemove', this.onDragMousemoveHandle)
Expand All @@ -29,6 +33,18 @@ function initDragHandle() {
this.mindMap.on('node_mouseup', this.onDragMouseupHandle)
}

// 收集同级节点宽度
function collectSiblingNodeWidths() {
this.referenceNodeWidths = []
if (this.parent && this.parent.children) {
this.parent.children.forEach(node => {
if (node !== this) {
this.referenceNodeWidths.push(node.width)
}
})
}
}

// 鼠标移动事件
function onDragMousemoveHandle(e) {
if (!this.isDragHandleMousedown) return
Expand All @@ -51,6 +67,25 @@ function onDragMousemoveHandle(e) {
let newWidth =
this.dragHandleMousedownCustomTextWidth +
(this.dragHandleIndex === 0 ? -ox : ox) / scaleX

// 吸附逻辑
const currentTotalWidth = newWidth + this.widthDiff
const SNAP_THRESHOLD = 5
let snapped = false
for (const width of this.referenceNodeWidths) {
if (Math.abs(currentTotalWidth - width) <= SNAP_THRESHOLD) {
newWidth = width - this.widthDiff
snapped = true
break
}
}

// 视觉反馈
if (this._dragHandleNodes && this._dragHandleNodes[this.dragHandleIndex]) {
this._dragHandleNodes[this.dragHandleIndex].fill({
color: snapped ? '#409eff' : 'transparent'
})
}
newWidth = Math.max(newWidth, minNodeTextModifyWidth)
if (maxNodeTextModifyWidth !== -1) {
newWidth = Math.min(newWidth, maxNodeTextModifyWidth)
Expand Down Expand Up @@ -87,6 +122,10 @@ function onDragMouseupHandle() {
this.dragHandleMousedownX = 0
this.dragHandleIndex = 0
this.dragHandleMousedownCustomTextWidth = 0
// 重置手柄颜色
if (this._dragHandleNodes) {
this._dragHandleNodes.forEach(node => node.fill({ color: 'transparent' }))
}
this.setData({
customTextWidth: this.customTextWidth
})
Expand Down Expand Up @@ -119,6 +158,9 @@ function createDragHandleNode() {
: this.customTextWidth
this.dragHandleMousedownBodyCursor = document.body.style.cursor
this.dragHandleMousedownLeft = this.left
// 计算差值和收集参考宽度
this.widthDiff = this.width - this.dragHandleMousedownCustomTextWidth
this.collectSiblingNodeWidths()
this.isDragHandleMousedown = true
})
})
Expand Down Expand Up @@ -146,6 +188,7 @@ function updateDragHandle() {

export default {
initDragHandle,
collectSiblingNodeWidths,
onDragMousemoveHandle,
onDragMouseupHandle,
createDragHandleNode,
Expand Down
113 changes: 99 additions & 14 deletions simple-mind-map/src/plugins/NodeImgAdjust.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class NodeImgAdjust {
constructor({ mindMap }) {
this.mindMap = mindMap
this.handleEl = null // 自定义元素,用来渲染临时图片、调整按钮
this.resizeBtnEl = null // 调整按钮元素
this.isShowHandleEl = false // 自定义元素是否在显示中
this.node = null // 当前节点实例
this.img = null // 当前节点的图片节点
Expand All @@ -21,6 +22,12 @@ class NodeImgAdjust {
this.currentImgWidth = 0 // 当前拖拽实时图片的大小
this.currentImgHeight = 0
this.isAdjusted = false // 是否是拖拽结束后的渲染期间
this.isAdjusted = false // 是否是拖拽结束后的渲染期间
this.referenceWidths = [] // 参考宽度列表
this.startClientX = 0
this.startClientY = 0
this.startImgWidth = 0
this.startImgHeight = 0
this.bindEvent()
}

Expand Down Expand Up @@ -164,6 +171,7 @@ class NodeImgAdjust {
cursor: nwse-resize;
`
btnEl.className = 'node-image-resize'
this.resizeBtnEl = btnEl
// 给按钮元素绑定事件
btnEl.addEventListener('mouseenter', () => {
// 移入按钮,会触发节点图片的移出事件,所以需要再次显示按钮
Expand Down Expand Up @@ -235,11 +243,37 @@ class NodeImgAdjust {
this.isMousedown = true
this.mousedownDrawTransform = this.mindMap.draw.transform()
// 隐藏节点实际图片
this.hideNodeImage()
// this.hideNodeImage()
this.mousedownOffset.x = e.clientX - this.rect.x2
this.mousedownOffset.y = e.clientY - this.rect.y2
// 记录初始状态
this.startClientX = e.clientX
this.startClientY = e.clientY
const { width, height } = this.node.getData('imageSize')
this.startImgWidth = width
this.startImgHeight = height
// 将节点图片渲染到自定义元素上
this.handleEl.style.backgroundImage = `url(${this.node.getData('image')})`
// this.handleEl.style.backgroundImage = `url(${this.node.getData('image')})`
// 收集参考宽度
this.referenceWidths = this.collectImageWidths()
}

// 收集所有带图片节点的宽度
collectImageWidths() {
const widths = new Set()
const walk = node => {
if (node.getData('image') && node !== this.node) {
const { width } = node.getData('imageSize') || {}
if (width) widths.add(width)
}
if (node.children) {
node.children.forEach(walk)
}
}
if (this.mindMap.renderer.root) {
walk(this.mindMap.renderer.root)
}
return Array.from(widths)
}

// 鼠标移动
Expand Down Expand Up @@ -279,32 +313,71 @@ class NodeImgAdjust {
imgMaxWidth = imgMaxWidth * scaleX
imgMaxHeight = imgMaxHeight * scaleY
// 计算当前拖拽位置对应的图片的实时大小
let newWidth = Math.abs(e.clientX - this.rect.x - this.mousedownOffset.x)
let newHeight = Math.abs(e.clientY - this.rect.y - this.mousedownOffset.y)
// 逻辑尺寸变化量
const deltaX = (e.clientX - this.startClientX) / scaleX
// 逻辑尺寸
let newWidth = this.startImgWidth + deltaX

// 转换为屏幕像素用于吸附判断
let screenWidth = newWidth * scaleX

// 吸附逻辑
const SNAP_THRESHOLD = 5
let snapped = false
for (const width of this.referenceWidths) {
const refScreenWidth = width * scaleX
if (Math.abs(screenWidth - refScreenWidth) <= SNAP_THRESHOLD) {
newWidth = width
screenWidth = refScreenWidth
snapped = true
break
}
}

let newHeight = newWidth / oRatio

// 限制最小值
if (newWidth < minImgResizeWidth) newWidth = minImgResizeWidth
if (newHeight < minImgResizeHeight) newHeight = minImgResizeHeight
// 限制最大值
if (newWidth > imgMaxWidth) newWidth = imgMaxWidth
if (newHeight > imgMaxHeight) newHeight = imgMaxHeight
const [actWidth, actHeight] = resizeImgSizeByOriginRatio(
imageOriginWidth,
imageOriginHeight,
newWidth,
newHeight
)
this.currentImgWidth = actWidth
this.currentImgHeight = actHeight
this.updateHandleElSize()

this.currentImgWidth = newWidth
this.currentImgHeight = newHeight

// 更新handleEl的位置和大小

this.node.getData().imageSize = {
width: this.currentImgWidth,
height: this.currentImgHeight,
custom: true
}
this.mindMap.render()

// 更新按钮状态
if (snapped) {
if (this.resizeBtnEl) {
this.resizeBtnEl.style.backgroundColor = '#409eff'
}
} else {
if (this.resizeBtnEl) {
this.resizeBtnEl.style.backgroundColor = 'rgba(0, 0, 0, 0.3)'
}
}
}

// 鼠标松开
onMouseup() {
if (!this.isMousedown) return
// 显示节点实际图片
this.showNodeImage()
// this.showNodeImage()
// 隐藏自定义元素
this.hideHandleEl()
// 重置按钮颜色
if (this.resizeBtnEl) {
this.resizeBtnEl.style.backgroundColor = 'rgba(0, 0, 0, 0.3)'
}
// 更新节点图片为新的大小
const { image, imageTitle } = this.node.getData()
const { scaleX, scaleY } = this.mousedownDrawTransform
Expand All @@ -331,6 +404,18 @@ class NodeImgAdjust {

// 渲染完成事件
onRenderEnd() {
if (this.isMousedown && this.node) {
// 拖拽过程中渲染完成,需要更新操作柄位置
// 必须重新获取 img 元素,因为 render 后 DOM 可能重建
const img = this.node.group.findOne('image')
if (img) {
this.img = img
this.rect = img.rbox()
// 强制更新位置,确保跟随
this.setHandleElRect()
}
return
}
if (!this.isAdjusted) {
this.hideHandleEl()
return
Expand Down