是否有可能使SVG在锐角周围与stroke-dasharray一致?

时间:2017-01-05 17:26:00

标签: css dom svg

我正在开发一个交互式应用程序,允许通过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>

ugly star

虽然摆弄可能每个手臂的笔划在这个特殊情况下使用stroke-dashoffset看起来一致,但我怀疑一个人可以在笔画中做出明显的转弯看起来一致一般鉴于stroke-dasharray的工作原理。但是,我的团队有一个内部需求要求它们保持一致,所以我必须对此进行调查。

所以要确认:是否有一般解决方案使用stroke-dasharray使尖角在尖角周围看起来一致?

3 个答案:

答案 0 :(得分:2)

对于具有不同线段长度的一般折线的情况,答案是&#34;不容易&#34;。

从技术上讲,它是可能的,但您必须创建一个破折号模式,该模式是折线的总长度,并单独列出每个破折号段。将图案的每个部分精心设计以适合每个线段的长度。这将导致非常长的破折号模式。显然,需要为每条折线单独生成。

&#13;
&#13;
<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;
&#13;
&#13;

答案 1 :(得分:2)

更新:此策略现在可用于svg <path>元素(包括曲线段),而不仅仅是<polygon>元素。我认为,通过微小的修改,它可以用于任何SVG形状,尽管我只在这里展示多边形和路径。

多边形(不需要任何polyfills)

以下函数允许您以编程方式计算所需的破折号和间隙长度,并将其作为stroke-dasharray应用于polygon元素。作为奖励,它还允许您选择是否要在所有角落(左图)或无角(右图)上划线。

enter image description here

对于每个多边形线段,函数计算线段的长度,然后计算开始和结束具有半长间隙的线段所需的短划线数。然后计算所需的精确短划线长度并创建必要的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.的屏幕截图。

enter image description here

除了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)

一种可能的解决方法是使用标记来模糊图形不一致性:

&#13;
&#13;
<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;
&#13;
&#13;