SVGPathUtils Test
撰写时间:2025-04-26
修订时间:2025-04-26
对SVG的SVGPathElement的d属性进行操控的工具类测试。
当前SVG2对此尚未统一规范。虽然Safari已经实现了特定的APIs,但由于Chrome尚未实现,因此不能直接使用Safari的这些APIs。
而通过正则表达式直接操控d属性的字符串,尤其是在动态交互的环境下,非常不便利,且代码散乱、不统一,极容易出现 bugs,加重了代码维护负担。因此根据当前的SVG2的规范,自行实现一些基本的数据包装,并针对各浏览器进行测试,可有效解决这些问题。
MoveToCommand
const { SVGUtils } = await import('/js/esm/SVGUtils.js');
const { SVGPathUtils, MoveToCommand } = await import('/js/esm/SVGPathUtils.js');
let path = SVGUtils.CreatePath("");
pc.assert(path.hasAttribute('d'));
pc.log('d = "%s"', SVGPathUtils.GetDString(path));
/* initial empty */
let moveToCommand = SVGPathUtils.GetMoveToCommand(path);
pc.assert(!moveToCommand);
/* set a moveToCommand while empty */
let newMoveToCommand = new MoveToCommand('M', 10, 10);
pc.log(newMoveToCommand);
SVGPathUtils.SetMoveToCommand(path, newMoveToCommand);
moveToCommand = SVGPathUtils.GetMoveToCommand(path);
pc.assert(moveToCommand);
pc.log(moveToCommand);
pc.assert(moveToCommand.commandName === newMoveToCommand.commandName && moveToCommand.x === newMoveToCommand.x && moveToCommand.y === newMoveToCommand.y);
pc.log('d = "%s"', SVGPathUtils.GetDString(path));
/* set a moveToCommand while none empty */
newMoveToCommand = new MoveToCommand('m', 25, 30);
SVGPathUtils.SetMoveToCommand(path, newMoveToCommand);
moveToCommand = SVGPathUtils.GetMoveToCommand(path);
pc.assert(moveToCommand.commandName === newMoveToCommand.commandName && moveToCommand.x === newMoveToCommand.x && moveToCommand.y === newMoveToCommand.y);
pc.log('d = "%s"', SVGPathUtils.GetDString(path));
/* mal-formmating */
path.setAttribute('d', 'L 10,10');
// expected moveto command. Parse error, but still set
/* set while has other cmds */
SVGPathUtils.SetMoveToCommand(path, newMoveToCommand);
const d = path.getAttribute('d');
pc.log({d});
LineToCommand
LineToCommand是允许有多点的类。
以一个点来初始化,然后再添加各个点:
let lineToCommand = new LineToCommand('L', 40, 10);
lineToCommand.appendItem(40, 40);
完整代码:
const { SVGUtils } = await import('/js/esm/SVGUtils.js');
const { SVGPathUtils, MoveToCommand, LineToCommand } = await import('/js/esm/SVGPathUtils.js');
let path = SVGUtils.CreatePath("M 10,10");
pc.log(path);
let lineToCommand = new LineToCommand('L', 40, 10);
lineToCommand.appendItem(40, 40);
pc.log(lineToCommand);
SVGPathUtils.AppendPathSeg(path, lineToCommand);
pc.log(path.getAttribute('d'));
function renderInSVG() {
let svg = SVGUtils.CreateSVG(50, 50);
svg.style = 'display: block; border: .1px solid gray;';
svg.appendChild(path);
pc.appendChild(svg);
pc.log(svg);
}
renderInSVG();
LineToCommand的pointList是SVGPointList的实例,可以直接通过它来操控各个点。为方便调用,LineToCommand的appendItem方法封装了此功能:
appendItem(x, y) {
let pt = SVGUtils.CreateSVGPoint(x, y);
this.pointList.appendItem(pt);
}
这样,代码就整合了SVGPointList及SVGPoint类,从而可以在需要时调用这些类的具体方法。
也可以直接使用二维数组来初始化多个点。
const { SVGUtils } = await import('/js/esm/SVGUtils.js');
const { SVGPathUtils, MoveToCommand, LineToCommand } = await import('/js/esm/SVGPathUtils.js');
let path = SVGUtils.CreatePath("M 10,10");
let lineToCommand = new LineToCommand('L', [
[40, 10], [40, 40], [10, 40]
]);
pc.log(lineToCommand);
SVGPathUtils.AppendPathSeg(path, lineToCommand);
pc.log(path.getAttribute('d'));
function renderInSVG() {
let svg = SVGUtils.CreateSVG(50, 50);
svg.style = 'display: block; border: .1px solid gray;';
svg.appendChild(path);
pc.appendChild(svg);
}
renderInSVG();
使用相对值来创建:
const { SVGUtils } = await import('/js/esm/SVGUtils.js');
const { SVGPathUtils, MoveToCommand, LineToCommand } = await import('/js/esm/SVGPathUtils.js');
let path = SVGUtils.CreatePath("M 10,10");
let lineToCommand = new LineToCommand('l', [
[30, 0], [0, 30], [-30, 0], [0, -30]
]);
pc.log(lineToCommand);
SVGPathUtils.AppendPathSeg(path, lineToCommand);
pc.log(path.getAttribute('d'));
function renderInSVG() {
let svg = SVGUtils.CreateSVG(50, 50);
svg.style = 'display: block; border: .1px solid gray;';
svg.appendChild(path);
pc.appendChild(svg);
}
renderInSVG();