调整画布大小时意外裁剪 SVG 图像

问题描述 投票:0回答:2

const pieceSVG = document.getElementById("pieceSVG");

// Convert the SVG element to a string to be converted to blob
var svgString = new XMLSerializer().serializeToString(pieceSVG);


//create our canvas
const canvas = document.getElementById("dummyCanvas");
//and context
const ctx = canvas.getContext("2d");

//create our image in blob format and then create url for it
const imageBlob = new Blob([svgString], {
  type: "image/svg+xml"
});
const imageUrl = URL.createObjectURL(imageBlob);

const img = new Image();
img.height = 100;
img.width = 100;
img.src = imageUrl;

//set width, if we are narrow it should be horizontal
const setCanvasWidth = () => {
  const width =
    window.innerWidth < 720 ? window.innerWidth * 0.6 : window.innerWidth / 10;

  return width;
};

//if wide it should be vertical
const setCanvasHeight = () => {
  const height =
    window.innerWidth > 720 ?
    window.innerHeight * 0.6 :
    window.innerHeight / 10;

  return height;
};

const setSize = () => {
  canvas.width = setCanvasWidth();
  canvas.height = setCanvasHeight();
};
//draw piece tied to the height if horizontal and to the width if horizontal
//draw piece tied to the height if horizontal and to the width if horizontal
const drawPiece = () => {
  if (!ctx) return;
  if (window.innerWidth < 720) {
    ctx.drawImage(img, 0, 0, 45, 45, 0, 0, canvas.height, canvas.height);
  } else {
    ctx.drawImage(img, 0, 0, 45, 45, 0, 0, canvas.width, canvas.width);
  }
};
//set our canvas size
setSize();

window.addEventListener("resize", setSize);

//set up our event loop to draw the piece
const eventLoop = () => {
  requestAnimationFrame(() => {
    drawPiece();
    eventLoop();
  });
};

eventLoop();
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Document</title>

</head>

<body>
  <canvas id="dummyCanvas" style="background-color: blue"></canvas>
  <svg id="pieceSVG" xmlns="http://www.w3.org/2000/svg" version="1.1" width="45" height="45">
  <g style="fill:#ffffff;stroke:#000000;stroke-width:1.5;stroke-linejoin:round">
    <path d="M 9,26 C 17.5,24.5 30,24.5 36,26 L 38.5,13.5 L 31,25 L 30.7,10.9 L 25.5,24.5 L 22.5,10 L 19.5,24.5 L 14.3,10.9 L 14,25 L 6.5,13.5 L 9,26 z"/>
    <path d="M 9,26 C 9,28 10.5,28 11.5,30 C 12.5,31.5 12.5,31 12,33.5 C 10.5,34.5 11,36 11,36 C 9.5,37.5 11,38.5 11,38.5 C 17.5,39.5 27.5,39.5 34,38.5 C 34,38.5 35.5,37.5 34,36 C 34,36 34.5,34.5 33,33.5 C 32.5,31 32.5,31.5 33.5,30 C 34.5,28 36,28 36,26 C 27.5,24.5 17.5,24.5 9,26 z"/>
    <path d="M 11.5,30 C 15,29 30,29 33.5,30" style="fill:none"/>
    <path d="M 12,33.5 C 18,32.5 27,32.5 33,33.5" style="fill:none"/>
    <circle cx="6" cy="12" r="2" />
    <circle cx="14" cy="9" r="2" />
    <circle cx="22.5" cy="8" r="2" />
    <circle cx="31" cy="9" r="2" />
    <circle cx="39" cy="12" r="2" />
  </g>
</svg>

</body>

</html>

我有一个正在绘制到 HTML5 画布上的 SVG 图像。我已将画布设置为响应屏幕宽度。我知道使用 css 样式设置高度和宽度会扭曲图像,所以我以相同的方式使用了 canvas.height = screenWidth / 15... 和 canvas.width。

我可以看到画布的高度和宽度是预期的大小,但是 SVG 没有绘制到正确的尺寸并意外切断。

//这是我为我的 piecesClass 绘制棋子的方法

 drawPieces() {
    drawTakenPieces(this.context, this.pieceArray, this.squareWidth);
  } 

调用这个函数

const drawTakenPieces = (
  ctx: CanvasRenderingContext2D,
  pieceArray: PieceOffBoard[],
  squareWidth: number
) => {
  pieceArray.forEach((piece) => {
    drawSquareOffBoard(piece.coord, squareWidth, ctx, "beige");

    drawPieceByCoord(piece.coord, squareWidth, piece, ctx);
  });
};

依次调用这个函数

//draws the piece by coordinate - full piece cannot be plugged in
export const drawPieceByCoord = (
  coord: Coord,
  squareWidth: number,
  piece: PieceType | PieceOffBoard,
  ctx: CanvasRenderingContext2D
) => {
  if (!ctx) return;
  //set our parameters

  const img = piece.image;

  //we need to adjust our x and y values so that the center of the picture is drawn on the central coord

  let { x, y } = coord;
  x = x - squareWidth / 2;
  y = y - squareWidth / 2;

  //draw that image to the canvas params 2-5 are for source pic, last 4 are for canvas drawing
  ctx.drawImage(img, 0, 0, 45, 45, x, y, squareWidth, squareWidth);
};

如您所见,我正在使用长格式的 drawImage api,因为我想保持比例正确。此外,我的 SVG 在 SVG 文件本身中被硬编码为 45 和 45 的尺寸,这就是为什么选择这些数字的原因。绘图功能与另一个 ctx 完美配合,即使在调整大小时,画布宽度和高度也相同。

我尝试修改一些缩放比例和各种组合,包括更基本的 api ctx.drawImage(img,0,0) 形式。我已经检查了堆栈溢出并尝试了 chatGPT 的运气,但没有得到任何看起来有效的东西。

我还控制台记录了画布宽度和我的方形宽度值,以确保我正确设置了这些并且它们是相同的减去一些舍入差异,我认为这是由于不使用像素的分数。

我希望有人能帮忙解决这个问题,因为我一直坚持我认为在最后一天非常简单和基本的东西。

请参阅下面的最小可重现示例。它所需要的只是一个带有 id dummyCanvas

画布的 html 文档
const pieceSVG = require("./assets/whitePieces/white_queen.svg");

//create our canvas
const canvas = document.getElementById("dummyCanvas") as HTMLCanvasElement;
//and context
const ctx = canvas.getContext("2d");

//create our image
const imageBlob = new Blob([pieceSVG], { type: "image/svg+xml" });
const imageUrl = URL.createObjectURL(imageBlob);

const img = new Image();
img.height = 100;
img.width = 100;
img.src = imageUrl;

//set width, if we are narrow it should be horizontal
const setCanvasWidth = (): number => {
  const width =
    window.innerWidth < 720 ? window.innerWidth * 0.6 : window.innerWidth / 10;

  return width;
};

//if wide it should be vertical
const setCanvasHeight = (): number => {
  const height =
    window.innerWidth > 720
      ? window.innerHeight * 0.6
      : window.innerHeight / 10;

  return height;
};

const setSize = () => {
  canvas.width = setCanvasWidth();
  canvas.height = setCanvasHeight();
};
//draw piece tied to the height if horizontal and to the width if horizontal
const drawPiece = () => {
  if (!ctx) return;
  if (window.innerWidth < 720) {
    ctx.drawImage(img, 0, 0, 45, 45, 0, 0, canvas.height, canvas.height);
  } else {
    ctx.drawImage(img, 0, 0, 45, 45, 0, 0, canvas.width, canvas.width);
  }
};
//set our canvas size
setSize();

window.addEventListener("resize", setSize);

//set up our event loop to draw the piece
const eventLoop = () => {
  requestAnimationFrame(() => {
    drawPiece();
    eventLoop();
  });
};

eventLoop();

svg html5-canvas resize drawimage
2个回答
0
投票

所以我确定了这个问题。通过 document.getElementById 获取元素时问题没有重现,但在通过 webpack 请求文件时仍然出现。

我使用 svg-inline-loader 加载我的 svgs,这反过来导致“宽度”和“高度”属性被剥离。如果没有此宽度和高度属性,则在缩放时没有绝对参考点可以使 ctx.drawImage 工作,从而导致截断。

这是通过在规则部分编辑我的 webpack.config.js 修复的。这是对我有用的配置,它允许我保留 svg 高度和宽度属性。

 {
        test: /\.svg$/,
        use: {
          loader: "svg-inline-loader",
          options: { removeSVGTagAttrs: false },
        },
      },

0
投票

您可能根本不需要调整大小事件侦听器。
(它们也可能对您的渲染性能产生重大影响)。

如果您的最终目标是保留固有的 svg 宽高比并添加一些缩放比例(例如,为了更高分辨率的输出),您可以尝试这种方法:

const pieceSVG = document.getElementById("pieceSVG");
// get svg viewBox
let vB = pieceSVG.viewBox.baseVal;

// check width/height attributes
let attW = +pieceSVG.getAttribute("width");
let attH = +pieceSVG.getAttribute("height");

// take either viewBox width/height or dimensions inherited from attributes
let svgW = vB.width ? vB.width : attW;
let svgH = vB.height ? vB.height : attH;
let aspect = svgW/svgH;

// expected result: 45x45
//console.log(aspect, svgW, svgH)

// Convert the SVG element to a string to be converted to blob
var svgString = new XMLSerializer().serializeToString(pieceSVG);

//create our canvas and context
const canvas = document.getElementById("dummyCanvas");

// desired width/height
let dpi = 1200;
let scale = dpi/72;
let w = svgW*scale;
let h = w / aspect;

canvas.width=w;
canvas.height=h;
const ctx = canvas.getContext("2d");

// create our image in blob format and then create url for it
const imageBlob = new Blob([svgString], {
  type: "image/svg+xml"
});
const imageUrl = URL.createObjectURL(imageBlob);

const img = new Image();
img.src = imageUrl;


img.onload = e=> {
  ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
};
.resize {
  padding: 1em;
  resize: both;
  border: 1px solid red;
  overflow: auto;
  width: 50%;
}


svg{
  background: #ccc;
}


svg,
canvas{
  width:45%;
}
  <div class="resize">

<canvas id="dummyCanvas" style="background-color: blue"></canvas>
  <svg id="pieceSVG" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 45 45">
  <g style="fill:#ffffff;stroke:#000000;stroke-width:1.5;stroke-linejoin:round">
    <path d="M 9,26 C 17.5,24.5 30,24.5 36,26 L 38.5,13.5 L 31,25 L 30.7,10.9 L 25.5,24.5 L 22.5,10 L 19.5,24.5 L 14.3,10.9 L 14,25 L 6.5,13.5 L 9,26 z"/>
    <path d="M 9,26 C 9,28 10.5,28 11.5,30 C 12.5,31.5 12.5,31 12,33.5 C 10.5,34.5 11,36 11,36 C 9.5,37.5 11,38.5 11,38.5 C 17.5,39.5 27.5,39.5 34,38.5 C 34,38.5 35.5,37.5 34,36 C 34,36 34.5,34.5 33,33.5 C 32.5,31 32.5,31.5 33.5,30 C 34.5,28 36,28 36,26 C 27.5,24.5 17.5,24.5 9,26 z"/>
    <path d="M 11.5,30 C 15,29 30,29 33.5,30" style="fill:none"/>
    <path d="M 12,33.5 C 18,32.5 27,32.5 33,33.5" style="fill:none"/>
    <circle cx="6" cy="12" r="2" />
    <circle cx="14" cy="9" r="2" />
    <circle cx="22.5" cy="8" r="2" />
    <circle cx="31" cy="9" r="2" />
    <circle cx="39" cy="12" r="2" />
  </g>
</svg>
</div>

上面的示例将尝试根据 svg 的固有尺寸获取纵横比 - 理想情况下基于

viewBox
属性 - 否则采用
width
height
属性。

很可能你需要增加画布像素分辨率,因为 svg 图标可能有非常小的边界框。

除非您通过 CSS 明确指定 with 和 height 属性,否则您的画布将保持所需的纵横比并响应视口变化。

© www.soinside.com 2019 - 2024. All rights reserved.