It is necessary to make it so that blocks regardless of width and length do not overlap with each other and do not go beyond the borders of the canvas when moving and when resizing. Blocks can be either full width or full length, and with the side of the minimum cell.
If anyone has a solution or at least the direction in which to move I will be very happy
Here is my code. It does not work quite correctly, because in some cases it jumps to the wrong zones behind the canvas or inside another block.
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>
Everything I’ve tried is listed in my code. The element rotation will not be used, I will exclude it from the library later.
2
Answers
I made a new arrangement of blocks. If there are more than two blocks, they start overlapping. I used your example as a basis. Thanks for your help.
GIF example
Here is something that I think might be what you call a jump to the wrong zone.
We can see I’m moving the left block horizontally but it jumps down to the bottom…
So I’m just going to focus on that horizontal movement problem, and that alone…
Looking at your code you have
setValidPosition
and inside that there is a condition:if (hasIntersection(target, obj)) {
I really could not understand your logic there, I think all that is needed there is set the position back to a known good state, there should not be any moving when there is an intersection.
Two things I will do:
First enable stateful on the canvas
then change the setValidPosition to
Here is the result, I’m still moving the left block horizontally but we can see that while the mouse is over the second block there is not jumping down like there was before
I removed some of your code to just focus on the movement and keep the code as small as possible, see working snippet below