How do I draw a Javascript-modified SVG object on

2019-08-29 09:45发布

The overall task I'm trying to achieve is to load an SVG image file, modify a color or text somewhere, and then draw it onto an HTML5 canvas (presumably with drawImage(), but any reasonable alternative would be fine).

I followed advice on another StackOverflow question on how to load and modify a SVG file in Javascript, which went like this:

<object class="svgClass" type="image/svg+xml" data="image.svg"></object>

followed in Javascript by

document.querySelector("object.svgClass").
    getSVGDocument().getElementById("svgInternalID").setAttribute("fill", "red")

And that works. I now have the modified SVG displaying in my web page.

But I don't want to just display it - I want to draw it as part of an HTML5 canvas update, like this:

ctx.drawImage(myModifiedSVG, img_x, img_y);

If I try storing the result of getSVGDocument() and passing that in as myModifiedSVG, I just get an error message.

How do I make the HTML5 canvas draw call for my modified SVG?


Edit: I can draw an SVG image on an HTML5 canvas already through doing this:

var theSVGImage = new Image();
theSVGImage.src = "image.svg";
ctx.drawImage(theSVGImage, img_x, img_y);

and that's great, but I don't know how to modify text/colors in my loaded SVG image that way! If someone could tell me how to do that modification, then that would also be a solution. I'm not tied to going through the object HTML tag.

1条回答
成全新的幸福
2楼-- · 2019-08-29 10:10

For a one shot, you could rebuild a new svg file, load it in an <img> and draw it again on the canvas:

async function doit() {
  const ctx = canvas.getContext('2d');
  const images = await prepareAssets();
  let i = 0;
  const size = canvas.width = canvas.height = 500;
  canvas.onclick = e => {
    i = +!i;
    ctx.clearRect(0, 0, size, size);
    ctx.drawImage(images[i], 0,0, size, size);
  };
  canvas.onclick();
  return images;
}

async function prepareAssets() {
  const svgDoc = await getSVGDOM();
  // There is no standard to draw relative sizes in canvas
  svgDoc.documentElement.setAttribute('width', '500');
  svgDoc.documentElement.setAttribute('height', '500');
  // generate the first <img> from current DOM state
  const originalImage = loadSVGImage(svgDoc);
  // here do your DOM manips
  svgDoc.querySelectorAll('[fill="#cc7226"]')
    .forEach(el => el.setAttribute('fill', 'lime'));
  // generate new <img>
  const coloredImage = loadSVGImage(svgDoc);
  
  return Promise.all([originalImage, coloredImage]);
}
  
function getSVGDOM() {
  return fetch('https://upload.wikimedia.org/wikipedia/commons/f/fd/Ghostscript_Tiger.svg')
    .then(resp => resp.text())
    .then(text => new DOMParser().parseFromString(text, 'image/svg+xml'));
}

function loadSVGImage(svgel) {
  // get the markup synchronously
  const markup = (new XMLSerializer()).serializeToString(svgel);
  const img = new Image();
  return new Promise((res, rej) => {
    img.onload = e => res(img);
    img.onerror = rej;
    // convert to a dataURI
    img.src=  'data:image/svg+xml,' + encodeURIComponent(markup);
  });
}

doit()
  .then(_ => console.log('ready: click to switch the image'))
  .catch(console.error);
<canvas id="canvas"></canvas>

But if you are going to do it with a lot of frames, and expect it to animate...

You will have to convert your svg into Canvas drawing operations.

The method above is asynchronous, so you cannot reliably generate new images on the fly and get it ready to be drawn in a single frame. You need to store a few of these ahead of time, but since how long it will take to load the image is completely random (at least it should be) this might be a real programming nightmare.
Add to that the overhead the browser will have in loading a whole new SVG document every frame (yes, browsers do load the SVG document even when loaded inside an <img>), then paint it on the canvas, and finally remove it from the memory which will get filled in no time, you won't have a much free CPU to do anything else.

So the best here is probably to parse your SVG and to convert it to CanvasRenderingContext2D drawing operations => Draw it yourself.

This is achievable, moreover now that we can pass d attributes directly into Path2D object constructor, and that most of SVG objects have correspondence in the Canvas2D API (we even can use SVG filters), but that's still a lot of work.

So you may want to look at libraries that do that. I'm not an expert in libraries myself, and I can't recommend any, but I know that canvg does that since a very long time, I just don't know if they do expose their js objects in a reusable way. I know that Fabric.js does, but it also comes with a lot of other features that you may not need.

The choice is yours.

查看更多
登录 后发表回答