I am creating a pannable, zoomable, infinite grid editor with fabric.js, typescript and vite. Since i implemented snapping to grid, it would be good to have a visual aid of where the grid actually is. I started with just simply making the grid (or field of dots, because that is nicer), with css, like this:
:root {
--dot-opacity: 40%; /* 22.5% */
--dot-color: hsla(0, 0%, 35%, var(--dot-opacity));
--dot-spacing: 16px;
--dot-size: 2px;
--main-bg: #19191b;
}
and added this to a wrapper element.
background-color: var(--main-bg);
background-repeat: repeat; */
background-image: radial-gradient(circle, var(--dot-color) var(--dot-size), transparent var(--dot-size));
background-size: var(--dot-spacing) var(--dot-spacing);
background-attachment: local;
background-position-x: calc(var(--dot-spacing) / 2) ;
background-position-y: calc(var(--dot-spacing) / 2) ; */
this works pretty well, but once you want to implement panning through fabric.js canvas.viewportTransform
, the dots no longer align.
I still want to retain the dynamic/parametric nature of the grid like i have now, but somehow use fabric.js to set the canvas background to a dot matrix that repeats and also pans with the canvas viewport-transform.
what i tried:
i made a svg with 4 circles, one in each corner, that only show 1/4 of each circle in the viewbox - a tile. there are 2 ways how i attempted to generate them, and both make valid svg:
const circlePositions = [[size, 0], [0, 0], [size, size], [0, size]]
const circleStyle = `fill:#ffffff;stroke:#9d5867;stroke-width:0;`
first attempt: through document.createElementNS
: (assingAttributesSVG just loops over Object.entries and uses svg.setAttribute)
const tileSvgElem = document.createElementNS("http://www.w3.org/2000/svg", "svg")
assignAttributesSVG(tileSvgElem, { width: size, height: size, viewBox: `0 0 ${size} ${size}`, version: "1.1" })
tileSvgElem.innerHTML = `<defs/><g>${
circlePositions
.map(([cx, cy]) => `<circle style="${circleStyle}" cx="${cx}" cy="${cy}" r="${r}"/>`)
.join("\n")
}</g></svg>`
second attempt: through plain string:
const tileSvgString = `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" version="1.1" xmlns="http://www.w3.org/2000/svg"><defs/><g>
${circlePositions.map(([cx, cy]) => `<circle style="${circleStyle}" cx="${cx}" cy="${cy}" r="${r}"/>`).join("\n")}
</g></svg>`
both of these do what they are expected, but i have trouble setting them as canvas background:
attempts to set it as background
- apparently fabric.js handles svg differently than images - i can't just set the svg element with
canvas.setBackgroundColor
orcanvas.SetBackgroundImage
. typescript told metypeof SVGSVGElement is not SVGImageElement
, or something along those lines. - i tried using a
fabric.Image
,fabric.Pattern
- also
fabric.loadSVGFromString
=>fabric.util.groupSVGElements(objects, options)
- also creating a
fabric.StaticCanvas
with.getElement()
i also tried inlining the svg as a data:svg
, with and without the url("")
:
function inlineSVGString(svgString: string) {
return `data:image/svg+xml;utf8,${encodeURIComponent(svgString)}`
}
function urlSVGString(svgString: string) {
return `url("${inlineSVGString(svgString)}")`
}
present
after all of this, i still cannot get a repeating dot-matrix/field of dots background for the fabric.js canvas. how do i do this?
there is probably some pretty obvious way to do this that i'm missing, i just picked up fabric.js a few days ago, but the docs are not that great, because they are autogenerated from JSDoc so im kind of relying on demos, tutorial, codepen examples and the typescript defs for fabric.js
other ideas:
- still use the old css approach, but use
canvas.on('mouse:move')
->this.viewportTransform
and setbackground-position-x
andbackground-position-y
to current transform % grid size