更新:此策略现在可用于 svg <path> 元素(包括曲线段),而不仅仅是 <polygon> 元素。我相信,只要稍加修改,它就可以用于任何 SVG 形状,尽管我在这里只对多边形和路径进行了演示。
多边形(不需要任何 polyfill)
下面的函数允许您以编程方式计算所需的破折号和间隙长度,并将它们作为stroke-dasharray 应用于polygon 元素。作为奖励,它还允许您选择是否要在所有角落(左图)或没有角落(右图)使用破折号。
对于每个多边形线段,该函数计算线段的长度,然后计算以半长间隙开始和结束该线段所需的破折号数。然后它会计算所需的确切破折号长度并创建必要的破折号/间隙stroke-dasharray。例如,如果有空间容纳 3 个长度为 d 的破折号(相当于 6 个破折号或间隙),则 stroke-dasharray 将是 d/2 d d d d d d/2。这将用半破折号开始和结束线段,如下所示:
xx----xxxx----xxxx----xx
对每条边执行此操作,将一条边的最后半长破折号与下一条边的前半长破折号结合起来,例如...
xx----xxxx----xxxx----xx + XXX------XXXXXX------XXXXXX------XXX
...变成...
xx----xxxx----xxxx----xxXXX------XXXXXX------XXXXXX------XXX
该函数还允许您将noneFlag 设置为true(默认为false),将笔划从在所有角落都有破折号转换为在没有角落有破折号。它通过简单地在stroke-dasharray 的开头添加一个零来实现这一点,有效地将所有破折号转换为空白,并将所有空白转换为破折号。每个生成的线段将如下所示:
--xxxx----xxxx----xxxx--
注意线段开始和结束处的半间隙(而不是半破折号)。
dashesAtCorners(document.querySelector('#one'), 5 );
dashesAtCorners(document.querySelector('#two'), 5, true);
function dashesAtCorners(polygon, aveDashSize, noneFlag) {
const coordinates = c = polygon.getAttribute('points').replace(/,| +/g, ' ')
.trim().split(' ').map(n => +n); // extract points' coordinates from polygon
c.push(c[0], c[1]); // repeat the 1st point's coordinates at the end
const dashes = d = noneFlag ? [0,0] : [0]; // if noneFlag, prepend extra zero
for (s = 0; s < c.length - 2; s += 2) { // s is line segment number * 2
const dx = c[s]-c[s+2], dy = c[s+1]-c[s+3], segLen = Math.sqrt(dx*dx+dy*dy),
numDashes = n = Math.floor(0.5 + segLen / aveDashSize / 2),
dashLen = len = segLen / n / 2; // calculate # of dashes & dash length
d.push((d.pop() + len) / 2); // join prev seg's last dash, this seg's 1st dash
(i => {while (i--) {d.push(len,len)}})(n); // fill out line with gaps & dashes
}
polygon.setAttribute('stroke-dasharray', d.join(' '));
}
<svg width="800" height="600" xmlns="http://www.w3.org/2000/svg">
<g stroke="red" stroke-width="20" transform="scale(0.7)">
<polygon id="one" transform="translate(-200, -40)"
points="350,75 379,161 469,161 397,215
423,301 350,250 277,301 303,215
231,161 321,161"
/>
<polygon id="two" transform="translate(100, -40)"
points="350,75 379,161 469,161 397,215
423,301 350,250 277,301 303,215
231,161 321,161"
/>
</g>
</svg>
上述策略通常可以应用于任何多边形元素,对称或不对称。例如,尝试任意更改示例星的任何坐标,它仍然可以工作。 (但是,请注意,使角太“尖”,即角度非常小,会改变其笔触外观,从而使角从“尖锐”变为“钝”。我不知道一般规则为此,例如角度阈值/截止。我也不知道在实现此限制时是否存在浏览器差异。您知道。除此之外,该策略通常适用于任何多边形。)
路径(需要 polyfill,截至 2017 年 2 月中旬)
但是,上述策略不能完全按照写入路径元素的方式应用。一个很大的区别是多边形只有直边,而路径也可以有曲线。将此策略应用于路径元素需要进行修改,以计算路径段的长度,如上述策略计算直多边形边的长度。对于路径,您需要检索单个(直线或弯曲)路径段,然后使用 getTotalLength 方法确定段长度。然后,您将以与上述代码使用每个直多边形边的长度相同的方式进行上述计算。唉,我们目前处于 2 个可用于此目的的 SVG API 之间的无人区:一个已弃用的旧的 (pathSegList) 和一个尚未可用的替代 (getPathData)。幸运的是,有polyfills for both the older and newer APIs 可以使用。请注意,getPathData polyfill 不能直接用于<use> 元素(尽管我想它可以用于<use> 元素使用的<defs> 部分中的形状元素,尽管我没有具体说明检查了这个)。
下图显示了来自this jsFiddle 使用the polyfill for getPathData, etc. 的屏幕截图。
撇开 polyfill,jsFiddle 的代码如下:
html:
<span>Set average dash length:</span><input type="range" min="4" max="60" value="60"/>
<span id="len">60</span>
<svg width="800" height="600" xmlns="http://www.w3.org/2000/svg">
<g stroke="red" stroke-width="20" transform="scale(0.7)">
<path id="one" transform="translate(-200, -40)"
d="M350,75 C
360,140 420,161 469,161
400,200 410,260 423,301
380,270 330,270 277,301
280,250 280,200 231,161
280,160 320,140 350,75
Z"
/>
<path id="two" transform="translate(200, -40)"
d="M350,75 C
360,140 420,161 469,161
400,200 410,260 423,301
380,270 330,270 277,301
280,250 280,200 231,161
280,160 320,140 350,75
Z"
/>
<path id="three" transform="translate(600, -40)"
d="M350,75 C
360,140 420,161 469,161
400,200 410,260 423,301
380,270 330,270 277,301
280,250 280,200 231,161
280,160 320,140 350,75
Z"
/>
</g>
<text transform="translate(55, 230)">Normal dashes</text>
<text transform="translate(330, 230)">Dashes at corners</text>
<text transform="translate(595, 230)">No dashes at corners</text>
</svg>
js:
setDashes(60);
document.querySelector('input').oninput = evt => {
const dashLen = evt.target.value;
document.querySelector('#len').innerHTML = dashLen;
setDashes(dashLen);
};
function setDashes(dashLen) {
document.querySelector('#one').setAttribute('stroke-dasharray', dashLen);
dashesAtCorners(document.querySelector('#two' ), dashLen );
dashesAtCorners(document.querySelector('#three'), dashLen, true);
}
function getSegLen(pathData, idx) {
const svgNS = "http://www.w3.org/2000/svg";
const currSeg = pathData[idx];
const prevSeg = pathData[idx - 1];
const prevSegVals = prevSeg.values;
const startCoords =
'M' +
prevSegVals[prevSegVals.length - 2] +
',' +
prevSegVals[prevSegVals.length - 1];
const segData = currSeg.type + currSeg.values;
const segD = startCoords + segData;
const newElmt = document.createElementNS(svgNS, "path");
newElmt.setAttributeNS(null, "d", segD);
return newElmt.getTotalLength();
}
function dashesAtCorners(element, aveDashSize, noneFlag) {
const pathData = element.getPathData();
const dashes = d = noneFlag ? [0,0] : [0]; // if noneFlag, prepend extra zero
const internalSegments = pathData.slice(1, -1);
for (segNum = 1; segNum < pathData.length - 1; segNum += 1) {
const segLen = getSegLen(pathData, segNum);
const numDashes = Math.floor(0.5 + segLen / aveDashSize / 2);
const dashLen = segLen / numDashes / 2;
// calculate # of dashes & dash length
dashes.push((dashes.pop() + dashLen) / 2); // join prev seg's last dash, this seg's 1st dash
(dashNum => {while (dashNum--) {dashes.push(dashLen,dashLen)}})(numDashes); // fill out line with gaps & dashes
}
element.setAttribute('stroke-dasharray', dashes.join(' '));
}