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();
所以我确定了这个问题。通过 document.getElementById 获取元素时问题没有重现,但在通过 webpack 请求文件时仍然出现。
我使用 svg-inline-loader 加载我的 svgs,这反过来导致“宽度”和“高度”属性被剥离。如果没有此宽度和高度属性,则在缩放时没有绝对参考点可以使 ctx.drawImage 工作,从而导致截断。
这是通过在规则部分编辑我的 webpack.config.js 修复的。这是对我有用的配置,它允许我保留 svg 高度和宽度属性。
{
test: /\.svg$/,
use: {
loader: "svg-inline-loader",
options: { removeSVGTagAttrs: false },
},
},
您可能根本不需要调整大小事件侦听器。
(它们也可能对您的渲染性能产生重大影响)。
如果您的最终目标是保留固有的 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 属性,否则您的画布将保持所需的纵横比并响应视口变化。