【问题标题】:fabric.js canvas repeating background with dynamic SVGfabric.js 画布用动态 SVG 重复背景
【发布时间】:2023-02-05 03:19:26
【问题描述】:

我正在使用 fabric.js、typescript 和 vite 创建一个可平移、可缩放、无限网格编辑器。 因为我实现了捕捉到网格,所以最好有一个网格实际位置的视觉帮助。 我开始只是用 css 简单地制作网格(或点域,因为这样更好),像这样:

:root {
    --dot-opacity: 40%; /* 22.5% */
    --dot-color: hsla(0, 0%, 35%, var(--dot-opacity));
    --dot-spacing: 16px; 
    --dot-size: 2px;
    --main-bg: #19191b;
}

并将其添加到包装器元素中。

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) ; */

这工作得很好,但是一旦你想通过 fabric.js canvas.viewportTransform 实现平移,点就不再对齐了。

我仍然想像现在一样保留网格的动态/参数特性,但以某种方式使用 fabric.js 将画布背景设置为重复的点矩阵,并使用画布视口变换进行平移。

我试过的:

我制作了一个带有 4 个圆圈的 svg,每个角一个,在视图框中只显示每个圆圈的 1/4 - 一个图块。 我尝试生成它们的方式有两种,并且都可以生成有效的 svg:

const circlePositions = [[size, 0], [0, 0], [size, size], [0, size]]
const circleStyle = `fill:#ffffff;stroke:#9d5867;stroke-width:0;`

第一次尝试:通过document.createElementNS:(assingAttributesSVG 只是循环 Object.entries 并使用 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>`

第二次尝试:通过纯字符串:

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>`

这两者都符合预期,但是我无法将它们设置为画布背景:

试图将其设置为背景

  • 显然 fabric.js 处理 svg 的方式与图像不同 - 我不能只使用 canvas.setBackgroundColorcanvas.SetBackgroundImage 设置 svg 元素。打字稿告诉我typeof SVGSVGElement is not SVGImageElement,或者类似的东西。
  • 我尝试使用fabric.Imagefabric.Pattern
  • 还有fabric.loadSVGFromString => fabric.util.groupSVGElements(objects, options)
  • 同时创建一个fabric.StaticCanvas.getElement()

我还尝试将 svg 内联为 data:svg,有和没有 url("")

function inlineSVGString(svgString: string) {
    return `data:image/svg+xml;utf8,${encodeURIComponent(svgString)}`
}
function urlSVGString(svgString: string) {
    return `url("${inlineSVGString(svgString)}")`
}

展示

毕竟,我仍然无法为 fabric.js 画布获得重复的点矩阵/点域背景。我该怎么做呢?

可能有一些非常明显的方法可以做到这一点,我错过了,我几天前刚拿起 fabric.js,但文档不是很好,因为它们是从 JSDoc 自动生成的,所以我有点依赖演示,教程、codepen 示例和 fabric.js 的 typescript defs

其他想法:

  • 仍然使用旧的 css 方法,但使用 canvas.on('mouse:move') -> this.viewportTransform 并将 background-position-xbackground-position-y 设置为当前变换 % 网格大小

【问题讨论】:

    标签: javascript typescript svg html5-canvas fabricjs


    【解决方案1】:

    解决了

    使用字符串尝试为点设置 ​​svg。 奇怪的是,您可以使用canvas.setBackgroundColor 来设置数据:图像。

    const circlePositions = [[size, 0], [0, 0], [size, size], [0, size]]
    const circleStyle = `fill:${getDotColor()};stroke:#9d5867;stroke-width:0;`
        
    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("
    ")}
    </g></svg>`
    
    canvasBgCallback = () => setTimeout(() => canvas.requestRenderAll(), 0) 
    //@ts-ignore
    canvas.setBackgroundColor({source: inlineSVGString(tileSvgString)}, canvasBgCallback)
    

    导致它的两个主要问题:

    • 当使用inlineSVGString而不是data:image/svg+xml,&lt;data&gt;时,我将其设置为使用utf-8,如下所示:data:image/svg+xml,utf8,不起作用。 data:image/svg+xml;utf8,作品有时但不可靠。
    • 设置背景颜色后,您已请求画布重新渲染。回调可能看起来很奇怪,但它是合理的:
      • 只是普通的旧canvas.requestRenderAll,因为回调有效,但它只会在您单击画布后呈现背景。这并不理想。 () =&gt; canvas.requestRenderAll() 或任何其他回调也会发生同样的事情。
      • 通过使用 0 的 setTimeout,我们可以使用一种 hacky 策略在所有其他事情完成后立即执行它。简短说明:js 是单线程的,这会在当前事件循环结束时安排回调。 longer answer
      • 我们还没有完成,因为使用setTimeout(() =&gt; canvas.requestRenderAll(), 0)作为回调并没有注册要运行的函数,它立即评估它并且setTimeout不返回函数,只有超时的id,导致错误
      • 这就是我们最终得出这个结论的原因:() =&gt; setTimeout(() =&gt; canvas.requestRenderAll(), 0)
      • 一个小警告是,如果您在函数或其他东西的中间初始化点阵,它会在那之后被初始化。所以只要确保你没有做任何依赖于在与这段代码相同的函数中绘制的点阵。但这在现实世界中不应该成为问题。

    【讨论】:

      猜你喜欢
      • 2014-05-17
      • 2013-09-24
      • 2013-10-11
      • 2018-04-25
      • 1970-01-01
      • 2016-01-03
      • 1970-01-01
      • 2021-11-26
      • 1970-01-01
      相关资源
      最近更新 更多