我正在开发一个交互式应用程序,允许通过 SVG 直接操作形状。我将从一个具体示例开始,然后提出一个一般性问题。给出的示例是它在 Chrome 中的呈现方式。
考虑到 的许多可能值stroke-dasharray,这位明星的手臂具有不一致的笔划。3 个边缘显得钝,2 个边缘显得锋利。stroke-linejoin会改变恒星的外观,但它并不能解决每条手臂上的不一致问题。
<svg width="800" height="600" xmlns="http://www.w3.org/2000/svg">
<polygon stroke="red"
stroke-width="20"
stroke-dasharray="5 5"
points="350,75 379,161 469,161 397,215
423,301 350,250 277,301 303,215
231,161 321,161" />
</svg>
Run Code Online (Sandbox Code Playgroud)
虽然在使用的特定情况下摆弄可能会使每只手臂的笔画看起来一致,但考虑到工作原理stroke-dashoffset,我怀疑人们能否使笔画的急转弯看起来保持一致。然而,我的团队内部要求使它们保持一致,所以我必须对此进行研究。stroke-dasharray
因此要确认:是否有一个通用的解决方案可以使用 使笔画在尖角周围显示一致stroke-dasharray?
更新:此策略现在可用于 svg<path>元素(包括曲线段),而不仅仅是<polygon>元素。我相信,只需稍加修改,它就可以用于任何 SVG 形状,尽管我在这里仅针对多边形和路径演示它。
多边形(不需要任何填充)
下面的函数允许您以编程方式计算所需的虚线和间隙长度,并将它们作为 a 应用于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
该函数还允许您将 设为noneFlagtrue(默认为 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(' '));
}Run Code Online (Sandbox Code Playgroud)
<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>Run Code Online (Sandbox Code Playgroud)
上述策略通常可以应用于任何多边形元素,无论对称还是不对称。例如,尝试任意更改示例星星的任何坐标,它仍然可以工作。(但是请注意,使角太“尖”,即角度非常小,会改变其笔划外观,从而使角从“尖”变为“钝”。我不知道一般规则,例如角度阈值/截止。我也不知道这个限制的实现是否存在浏览器差异。只是让你知道。但是除此之外,该策略通常适用于任何多边形。)
路径(需要一个 polyfill,截至 2017 年 2 月中旬)
然而,上述策略不能完全按照写入路径元素的方式应用。一个很大的区别是多边形只有直边,而路径也可以有曲线。将此策略应用于路径元素需要进行修改,以计算路径段的长度,就像上述策略计算直多边形边的长度一样。对于路径,您需要检索各个(直线或曲线)路径段,然后使用该getTotalLength方法确定段长度。然后,您将以与上述代码使用每个直多边形边的长度相同的方式继续进行上述计算。唉,我们目前正处于两个可用于此目的的 SVG API 之间的无人区:一个较旧的已弃用的 API ( pathSegList) 和一个尚不可用的替代 API ( getPathData)。幸运的是,有适用于旧版和新版 API 的 Polyfill可供使用。请注意,getPathDatapolyfill 不能直接在元素上使用<use>(尽管我想,它可以在元素使用<defs>的部分中的形状元素上使用<use>,尽管我没有专门检查这一点)。
下图显示了此 jsFiddle使用polyfill forgetPathData等的屏幕截图。
抛开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>
Run Code Online (Sandbox Code Playgroud)
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(' '));
}
Run Code Online (Sandbox Code Playgroud)