fabric.js 库中画布内矩形块的正确移动

问题描述 投票:0回答:1

必须确保块无论宽度和长度如何都不会相互重叠,并且在移动和调整大小时不会超出画布的边界。块可以是全宽或全长,并且具有最小单元的侧面。 如果有人有解决方案或至少有移动的方向,我会很高兴

这是我的代码。它不能正常工作,因为在某些情况下它会跳转到画布后面或另一个块内的错误区域。

const canvasSize = { width: 1923, height: 1083 }
const cellDimensions = { width: 120, height: 120 }
const borderStrokeWidth = 3
const objectTypes = {
    frame: 'frame',
    gridPoint: 'point',
    gridLine: 'line',
}

const initialFramesList = [
    {
        top: 120,
        left: 120,
        width: 240,
        height: 480,
    },
    {
        top: 120,
        left: 600,
        width: 480,
        height: 720,
    },
]

let drawingCanvas

initCanvas()
initFrames(initialFramesList)

function initCanvas(id = 'canvas') {
    let startX
    let startY
    let endX
    let endY
    let activeFrame

    drawingCanvas = new fabric.Canvas(id, {
        height: canvasSize.height + borderStrokeWidth,
        width: canvasSize.width + borderStrokeWidth,
        hoverCursor: 'default',
        backgroundColor: 'gray',
        selection: false,
    })

    const onClickPoint = (e) => {
        const isGridPoint = e.target?.type === objectTypes.gridPoint

        if (isGridPoint && !activeFrame) {
            const { top: y, left: x, width: d } = e.target
            const r = d / 2
            startX = x + r
            startY = y + r
            activeFrame = createFrame({ x: startX, y: startY })
            drawingCanvas.add(activeFrame)
        } else if (activeFrame) {
            startX = null
            startY = null
            endX = null
            endY = null
            activeFrame.setCoords()
            activeFrame = null
            drawingCanvas.renderAll()
        }
    }

    const widgetsLoop = (fn) => {
        const widgets = drawingCanvas.getObjects(objectTypes.frame)
        widgets.forEach((obj) => {
            fn(obj)
        })
    }

    const getAbsolutePosition = (el) => {
        return el.group
            ? { x: el.group.left, y: el.group.top }
            : { x: el.left, y: el.top }
    }

    const hasIntersection = (target, obj) => {
        const { x: targetLeft, y: targetTop } = getAbsolutePosition(target)
        const { x: objLeft, y: objTop } = getAbsolutePosition(obj)
        const {
            width: targetWidth,
            height: targetHeight,
            originX: targetOriginX,
            originY: targetOriginY,
        } = target
        const { width: objWidth, height: objHeight } = obj

        const rectLeftCorrect =
            targetOriginX === 'right'
                ? targetLeft - targetWidth - borderStrokeWidth
                : targetLeft
        const rectTopCorrect =
            targetOriginY === 'bottom'
                ? targetTop - targetHeight - borderStrokeWidth
                : targetTop

        const xIntersection =
            rectLeftCorrect + targetWidth > objLeft &&
            rectLeftCorrect < objLeft + objWidth
        const yIntersection =
            rectTopCorrect + targetHeight > objTop &&
            rectTopCorrect < objTop + objHeight

        return xIntersection && yIntersection
    }

    const limitFrameMoving = (target) => {
        const { width: screenW, height: screenH } = canvasSize
        const { x: rectLeft, y: rectTop } = getAbsolutePosition(target)
        const {
            originX: targetOriginX,
            originY: targetOriginY,
            width: targetW,
            height: targetH,
        } = target
        const rectLeftCorrect =
            targetOriginX === 'right'
                ? rectLeft - targetW - borderStrokeWidth
                : rectLeft
        const rectTopCorrect =
            targetOriginY === 'bottom'
                ? rectTop - targetH - borderStrokeWidth
                : rectTop

        if (rectLeftCorrect < 0) {
            target.set({ left: 0 })
        } else if (rectLeftCorrect + targetW > screenW) {
            target.set({ left: screenW - targetW })
        }

        if (rectTopCorrect < 0) {
            target.set({ top: 0 })
        } else if (rectTopCorrect + targetH > screenH) {
            target.set({ top: screenH - targetH })
        }
    }

    const setValidSize = (target) => {
        const { width: cellW, height: cellH } = cellDimensions

        widgetsLoop((obj) => {
            if (obj === target) {
                return
            }

            if (hasIntersection(target, obj)) {
                const { x: rectLeft, y: rectTop } = getAbsolutePosition(target)
                const { x: objLeft, y: objTop } = getAbsolutePosition(obj)
                const { originX: targetOriginX, originY: targetOriginY } =
                    target
                const { width: objWidth, height: objHeight } = obj

                const rectLeftCorrect =
                    targetOriginX === 'right'
                        ? rectLeft - objWidth - borderStrokeWidth
                        : rectLeft
                const rectTopCorrect =
                    targetOriginY === 'bottom'
                        ? rectTop - objHeight - borderStrokeWidth
                        : rectTop

                const dx = Math.abs(rectLeftCorrect - objLeft)
                const dy = Math.abs(rectTopCorrect - objTop)

                if (dx > dy && target.width > dx) {
                    target.set({ width: dx, cellY: dx / cellW })
                } else if (dx < dy && target.height > dy) {
                    target.set({ height: dy, cellY: dy / cellH })
                }
            }
        })
    }

    const setValidPosition = (target) => {
        widgetsLoop((obj) => {
            if (obj === target) {
                return
            }

            limitFrameMoving(target)

            if (hasIntersection(target, obj)) {
                const dx = Math.abs(target.left - obj.left)
                const dy = Math.abs(target.top - obj.top)
                if (dx > dy) {
                    target.set({ left: obj.left - target.width })
                    if (target.left < 0) {
                        target.set({ left: obj.left + obj.width })
                    }
                } else {
                    target.set({ top: obj.top - target.height })
                    if (target.top < 0) {
                        target.set({ top: obj.top + obj.height })
                    }
                }
            }
        })
        drawingCanvas.renderAll()
    }

    drawingCanvas.on('mouse:over', (e) => {
        if (e.target?.type === objectTypes.gridPoint) {
            drawingCanvas.bringToFront(e.target)
            e.target.set('opacity', 1)
            drawingCanvas.renderAll()
        }
    })

    drawingCanvas.on('mouse:out', (e) => {
        if (e.target?.type === objectTypes.gridPoint) {
            e.target.set('opacity', 0)
            drawingCanvas.renderAll()
        }
    })

    drawingCanvas.on('mouse:down', (e) => {
        onClickPoint(e)
    })

    drawingCanvas.on('mouse:move', (e) => {
        if (!activeFrame) {
            return
        }

        const { width: cellW, height: cellH } = cellDimensions
        const pointerX = Math.floor(e.pointer.x / cellW) * cellW
        const pointerY = Math.floor(e.pointer.y / cellH) * cellH

        const width = pointerX - startX + cellW
        const height = pointerY - startY + cellH

        if (pointerX >= startX) {
            activeFrame.set({ width, cellX: width / cellW })
        }

        if (pointerY >= startY) {
            activeFrame.set({ height, cellY: height / cellH })
        }

        setValidSize(activeFrame)

        drawingCanvas.renderAll()
    })

    drawingCanvas.on('object:scaling', (e) => {
        const target = e.target
        const { width: cellW, height: cellH } = cellDimensions
        const width = Math.round((target.width * target.scaleX) / cellW) * cellW
        const height =
            Math.round((target.height * target.scaleY) / cellH) * cellH
        const { originX, originY, corner } = e.transform
        const { y: top, x: left } = target.getPointByOrigin(originX, originY)

        target.set({
            originX,
            originY,
            left,
            top,
            scaleX: 1,
            scaleY: 1,
            width: width > cellW ? width : cellW,
            height: height > cellH ? height : cellH,
            cellX: width / cellW,
            cellY: height / cellH,
            lockScalingFlip: true,
        })

        setValidSize(target)
    })

    drawingCanvas.on('object:moving', (e) => {
        const target = e.target
        const { width: cellW, height: cellH } = cellDimensions
        const left = Math.floor(target.left / cellW) * cellW
        const top = Math.floor(target.top / cellH) * cellH
        target.set({
            left,
            top,
        })
        setValidPosition(target)
    })

    drawingCanvas.on('object:modified', (e) => {
        const obj = e.target
        const { y: top, x: left } = obj.getPointByOrigin('left', 'top')
        obj.set({ top, left, originX: 'left', originY: 'top' })
        drawingCanvas.renderAll()
    })

    drawGrid()
}

function drawGrid() {
    const { width: colSize, height: rowSize } = cellDimensions

    const makePoint = (x, y) => {
        const r = 8
        const point = new fabric.Rect({
            top: y - r,
            left: x - r,
            width: r * 2,
            height: r * 2,
            rx: 1,
            ry: 1,
            fill: 'black',
            hasControls: false,
            hasBorders: false,
            selectable: false,
            opacity: 0,
            type: objectTypes.gridPoint,
            hoverCursor: 'pointer',
        })

        drawingCanvas.add(point)
    }

    const drawLine = (params) => {
        const line = new fabric.Line(params, {
            stroke: 'yellow',
            borderStrokeWidth: borderStrokeWidth,
            selectable: false,
            evented: false,
            type: objectTypes.gridLine,
        })
        drawingCanvas.add(line)
    }

    const makeRows = () => {
        const rowsCount = canvasSize.height / rowSize
        for (let i = 0; i < rowsCount; i++) {
            const y = i * rowSize
            drawLine([0, y, canvasSize.width, y])
        }
    }

    const makeCols = () => {
        const colsCount = canvasSize.width / colSize
        for (let i = 0; i < colsCount; i++) {
            const x = i * colSize
            drawLine([x, 0, x, canvasSize.height])
        }
    }

    const makePoints = () => {
        const rowsCount = canvasSize.height / rowSize
        const colsCount = canvasSize.width / colSize
        for (let i = 0; i < colsCount; i++) {
            const x = i * colSize
            for (let j = 0; j < rowsCount; j++) {
                const y = j * rowSize
                makePoint(x, y)
            }
        }
    }

    makeRows()
    makeCols()
    makePoints()
}

function initFrames(frames) {
    const { width: cellW, height: cellH } = cellDimensions
    frames.forEach(({ top, left, width, height }) => {
        const frame = createFrame(
            {
                x: left,
                y: top,
            },
            {
                width: width,
                height: height,
            }
        )
        frame.set({
            cellX: width / cellW,
            cellY: height / cellH,
            lockScalingFlip: true,
        })
        drawingCanvas.add(frame)
    })
}

function createFrame({ x, y }, { width, height } = { width: 0, height: 0 }) {
    const rect = new fabric.Rect({
        top: y,
        left: x,
        width,
        height,
        stroke: 'gray',
        borderStrokeWidth: borderStrokeWidth,
        fill: 'white',
        type: objectTypes.frame,
        strokeUniform: true,
        noScaleCache: false,
        objectCaching: false,
    })

    return rect
}
<canvas id="canvas"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.4.0/fabric.min.js"></script>

我尝试过的所有内容都列在我的代码中。元素旋转不会使用,稍后我会将其从库中排除。

javascript algorithm canvas fabricjs
1个回答
0
投票

我认为这可能就是你所谓的跳转到错误区域。

我们可以看到我正在移动左侧的方块,但它跳到了底部......

所以我只关注这个问题
查看您的代码,您有

setValidPosition
,其中有一个条件:
if (hasIntersection(target, obj)) {

我真的无法理解你的逻辑,我认为所需要做的就是将位置设置回已知的良好状态,当有交叉点时不应该有任何移动。

我做的两件事:
首先在画布上启用有状态

drawingCanvas.stateful = true;

然后将 setValidPosition 更改为

const setValidPosition = (target) => {
  widgetsLoop((obj) => {
    if (obj === target) {
      return
    }
    if (hasIntersection(target, obj)) {
      target.set({ left: target._stateProperties.left, top: target._stateProperties.top })
    } else {
      target.saveState();
    }
  })
  drawingCanvas.renderAll()
}

const canvasSize = { width: 520, height: 400 }
const cellDimensions = { width: 40, height: 40 }
const borderStrokeWidth = 3
const objectTypes = {frame: 'frame', gridPoint: 'point', gridLine: 'line'}

const initialFramesList = [
    {top: 40, left: 200, width: 80, height: 200},
    {top: 40, left: 40, width: 80, height: 120}
]

let drawingCanvas

initCanvas()
initFrames(initialFramesList)

function initCanvas(id = 'canvas') {
    let startX
    let startY
    let endX
    let endY
    let activeFrame

    drawingCanvas = new fabric.Canvas(id, {
        height: canvasSize.height + borderStrokeWidth,
        width: canvasSize.width + borderStrokeWidth,
        hoverCursor: 'default',
        backgroundColor: 'gray',
        selection: false,
    })
    drawingCanvas.stateful = true;

    const widgetsLoop = (fn) => {
        const widgets = drawingCanvas.getObjects(objectTypes.frame)
        widgets.forEach((obj) => { fn(obj) })
    }

    const getAbsolutePosition = (el) => {
        return el.group
            ? { x: el.group.left, y: el.group.top }
            : { x: el.left, y: el.top }
    }

    const hasIntersection = (target, obj) => {
        const { x: targetLeft, y: targetTop } = getAbsolutePosition(target)
        const { x: objLeft, y: objTop } = getAbsolutePosition(obj)
        const {
            width: targetWidth,
            height: targetHeight,
            originX: targetOriginX,
            originY: targetOriginY,
        } = target
        const { width: objWidth, height: objHeight } = obj

        const rectLeftCorrect =
            targetOriginX === 'right'
                ? targetLeft - targetWidth - borderStrokeWidth
                : targetLeft
        const rectTopCorrect =
            targetOriginY === 'bottom'
                ? targetTop - targetHeight - borderStrokeWidth
                : targetTop

        const xIntersection =
            rectLeftCorrect + targetWidth > objLeft &&
            rectLeftCorrect < objLeft + objWidth
        const yIntersection =
            rectTopCorrect + targetHeight > objTop &&
            rectTopCorrect < objTop + objHeight
        
        return xIntersection && yIntersection
    }

    const setValidPosition = (target) => {
        widgetsLoop((obj) => {
            if (obj === target) {
                return
            }
            if (hasIntersection(target, obj)) {
                target.set({ 
                  left: target._stateProperties.left, 
                  top: target._stateProperties.top, })
            } else {
                target.saveState();
            }
        })
        drawingCanvas.renderAll()
    }

    drawingCanvas.on('mouse:over', (e) => {
        if (e.target?.type === objectTypes.gridPoint) {
            drawingCanvas.bringToFront(e.target)
            e.target.set('opacity', 1)
            drawingCanvas.renderAll()
        }
    })

    drawingCanvas.on('mouse:out', (e) => {
        if (e.target?.type === objectTypes.gridPoint) {
            e.target.set('opacity', 0)
            drawingCanvas.renderAll()
        }
    })

    drawingCanvas.on('object:moving', (e) => {
        const target = e.target
        const { width: cellW, height: cellH } = cellDimensions
        const left = Math.floor(target.left / cellW) * cellW
        const top = Math.floor(target.top / cellH) * cellH
        target.set({ left, top })
        setValidPosition(target)
    })

    drawingCanvas.on('object:modified', (e) => {
        const obj = e.target
        const { y: top, x: left } = obj.getPointByOrigin('left', 'top')
        obj.set({ top, left, originX: 'left', originY: 'top' })
        drawingCanvas.renderAll()
    })

    drawGrid()
}

function drawGrid() {
    const { width: colSize, height: rowSize } = cellDimensions

    const drawLine = (params) => {
        const line = new fabric.Line(params, {
            stroke: 'yellow',
            borderStrokeWidth: borderStrokeWidth,
            selectable: false,
            evented: false,
            type: objectTypes.gridLine,
        })
        drawingCanvas.add(line)
    }

    const makeRows = () => {
        const rowsCount = canvasSize.height / rowSize
        for (let i = 0; i < rowsCount; i++) {
            const y = i * rowSize
            drawLine([0, y, canvasSize.width, y])
        }
    }

    const makeCols = () => {
        const colsCount = canvasSize.width / colSize
        for (let i = 0; i < colsCount; i++) {
            const x = i * colSize
            drawLine([x, 0, x, canvasSize.height])
        }
    }

    makeRows()
    makeCols()
}

function initFrames(frames) {
    const { width: cellW, height: cellH } = cellDimensions
    frames.forEach(({ top, left, width, height }) => {
        const frame = createFrame(
            { x: left, y: top },
            { width: width, height: height }
        )
        frame.set({
            cellX: width / cellW,
            cellY: height / cellH,
            lockScalingFlip: true,
        })
        drawingCanvas.add(frame)
    })
}

function createFrame({ x, y }, { width, height } = { width: 0, height: 0 }) {
    const rect = new fabric.Rect({
        top: y,
        left: x,
        width,
        height,
        stroke: 'gray',
        borderStrokeWidth: borderStrokeWidth,
        fill: 'white',
        type: objectTypes.frame,
        strokeUniform: true,
        noScaleCache: false,
        objectCaching: false,
    })
    drawingCanvas.setActiveObject(rect)
    return rect
}
<canvas id="canvas"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.4.0/fabric.min.js"></script>

© www.soinside.com 2019 - 2024. All rights reserved.