WebGL Tutorial
and more

SVGPathUtils Test

撰写时间:2025-04-26

修订时间:2025-04-26

SVGSVGPathElementd属性进行操控的工具类测试。

当前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();

LineToCommandpointListSVGPointList的实例,可以直接通过它来操控各个点。为方便调用,LineToCommandappendItem方法封装了此功能:

appendItem(x, y) { let pt = SVGUtils.CreateSVGPoint(x, y); this.pointList.appendItem(pt); }

这样,代码就整合了SVGPointListSVGPoint类,从而可以在需要时调用这些类的具体方法。

也可以直接使用二维数组来初始化多个点。

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();

参考资源

  1. SVG2 The grammar ofr path data