Отображение элемента который генерируется внутри canvas за его пределами fabricjs

Сейчас при наведении на пересечение линий отображается черная квадратная точка. Внутри канваса все хорошо, она отображается полностью, но если навести на край блока где так же есть пересечение, отображение квадратной точки происходит строго по границам канваса. Есть ли какой то способ отобразить черную квадратную точку при наведении за пределами канваса с любой из сторон.

Как работает сейчас
Как надо чтобы отображалось

const canvasSize = { width: 500, height: 500 }
const cellDimensions = { width: 50, height: 50 }
const borderStrokeWidth = 3
const objectTypes = {
    frame: 'frame',
    gridPoint: 'point',
    gridLine: 'line',
}

let drawingCanvas

initCanvas()

function initCanvas(id = 'canvas') {

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

   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: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',
            strokeWidth: 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()
}
<canvas id="canvas"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.4.0/fabric.min.js"></script>


Ответы (1 шт):

Автор решения: UModeL

Самое очевидное решение - это увеличить холст по высоте и ширине на диаметр точки. При этом сместить отрисовку остальных элементов на радиус точки.

const r = 8;
const canvasSize = {
  width: 500 + r * 2,
  height: 500 + r * 2
};
const cellDimensions = {
  width: 50,
  height: 50
};
const borderStrokeWidth = 3;
const objectTypes = {
  frame: 'frame',
  gridPoint: 'point',
  gridLine: 'line'
};

let drawingCanvas;

initCanvas();

function initCanvas(id = 'canvas') {

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

  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: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;

  /* Добавление фона фигурой вместо backgroundColor у всего холста */
  const drawBack = () => {
    const rect = new fabric.Rect({
      left: r,
      top: r,
      fill: 'grey',
      width: canvasSize.width - r * 2,
      height: canvasSize.height - r * 2
    });
    drawingCanvas.add(rect);
  }  
  
  const makePoint = (x, y) => {
    // const r = 18; Перенесено в глобальную область
    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',
      strokeWidth: borderStrokeWidth,
      selectable: false,
      evented: false,
      type: objectTypes.gridLine,
    })
    drawingCanvas.add(line);
  }

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

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

  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 + r, y + r);
      }
    }
  }

  drawBack();
  makeRows();
  makeCols();
  makePoints();
}
<canvas id="canvas"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.4.0/fabric.min.js"></script>

Вынес константу r (радиус точки) за пределы функций, чтобы она была видна во всех расчётах. А также заменил фоновый цвет холста на прозрачный, подложив под сетку прямоугольник нужного цвета.

→ Ссылка