我正在开发一个交互式应用程序,允许通过SVG直接操作形状。我将从一个具体的例子开始,然后问一般问题。给出的示例是它在Chrome中的呈现方式。
鉴于stroke-dasharray
的许多可能值,this star's臂具有不一致的笔画。 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>
虽然摆弄可能每个手臂的笔划在这个特殊情况下使用stroke-dashoffset
看起来一致,但我怀疑一个人可以在笔画中做出明显的转弯看起来一致一般鉴于stroke-dasharray
的工作原理。但是,我的团队有一个内部需求要求它们保持一致,所以我必须对此进行调查。
所以要确认:是否有一般解决方案使用stroke-dasharray
使尖角在尖角周围看起来一致?
答案 0 :(得分:2)
对于具有不同线段长度的一般折线的情况,答案是&#34;不容易&#34;。
从技术上讲,它是可能的,但您必须创建一个破折号模式,该模式是折线的总长度,并单独列出每个破折号段。将图案的每个部分精心设计以适合每个线段的长度。这将导致非常长的破折号模式。显然,需要为每条折线单独生成。
<svg width="400" height="400">
<polygon points="50,100, 200,300, 350,300, 350,100"
stroke="red" stroke-width="30"
stroke-dasharray="50 50 50 50
100
50
90
40 40 40
100
60 60 60 60 0"/>
</svg>
&#13;
答案 1 :(得分:2)
更新:此策略现在可用于svg <path>
元素(包括曲线段),而不仅仅是<polygon>
元素。我认为,通过微小的修改,它可以用于任何SVG形状,尽管我只在这里展示多边形和路径。
多边形(不需要任何polyfills)
以下函数允许您以编程方式计算所需的破折号和间隙长度,并将其作为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>
上述策略通常可应用于任何多边形元素,对称或不对称。例如,尝试任意改变示例星的任何坐标,它仍然可以工作。 (但是,请注意,使角落过于“尖”,即角度非常小,会改变其笔触外观,使角落从“尖锐”变为“钝”。我不知道一般规则为此,例如角度阈值/截止。我也不知道在这个限制的实现中是否存在浏览器差异。只是你知道。但是,除此之外,策略通常适用于任何多边形。)
路径(需要填充,截至2017年2月中旬)
然而,上述策略不能完全按照写入路径元素的方式应用。一个很大的区别是多边形只有直边,而路径也有曲线。将此策略应用于路径元素需要进行修改以计算路径段的长度,如上述策略计算直线多边形边的长度。对于路径,您需要检索单个(直线或弯曲)路径段,然后使用getTotalLength
方法确定段长度。然后,您将继续进行上述计算,方法与上面的代码使用每个直边多边形边的长度相同。唉,我们目前处于两个可用于此目的的SVG API之间的无人区:一个较旧的已弃用(pathSegList
)和一个尚未可用的替换(getPathData
)。幸运的是,可以使用polyfills for both the older and newer APIs。请注意,getPathData
polyfill不能直接用于<use>
元素(尽管我可以在<defs>
部分的<use>
部分中使用<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>
元素使用,虽然我没有专门检查过这个。)
下图显示this jsFiddle使用the polyfill for getPathData
, etc.的屏幕截图。
除了polyfill之外,来自jsFiddle的代码如下:
<强> HTML:强>
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(' '));
}
<强> JS:强>
{{1}}
答案 2 :(得分:0)
一种可能的解决方法是使用标记来模糊图形不一致性:
<svg width="800" height="600" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="red-circle"
viewBox="0 0 10 10" refX="5" refY="5" orient="auto" >
<circle fill="red" cx="5" cy="5" r="5"/>
</marker>
</defs>
<polygon filter="url(#trythis)" 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"
marker-mid="url(#red-circle)"
marker-end="url(#red-circle)"/>
</svg>
&#13;