首页 > 解决方案 > 无论位置如何,如何获得一致的笔画宽度?

问题描述

问题的本质是,当我移动 SVG 元素时,线条的宽度会发生变化。

我在浏览器环境中工作,我想要清晰的线条(没有抗锯齿),所以形状渲染设置为清晰边缘。例如,您可能有

“d M 0 0 L 10 0 L 10 10 L 0 10 z”

定义一个正方形,样式设置为

'填写:无;中风:黑色;笔画宽度:1px;形状渲染:crispEdges'

然后使用 translate() 用鼠标拖动它。它可以工作,但是随着正方形的移动,线条的粗细会跳动一个像素,我希望粗细保持不变。

我想我知道它为什么会发生,但我找不到任何方法来阻止它。它发生(我认为)是由于相对于 viewBox 的坐标映射到相对于物理监视器像素的坐标的方式。当一个像素宽(在 SVG 术语中)的线被映射到显示器时,它可能会或可能不会跨越多个像素,因此它最终可能会变得更厚或更薄(屏幕)像素。

我尝试弄乱 viewBox 大小与绘图区域大小的比率,将平移量四舍五入为整数或采用整数 + 0.50 的形式,但这样的解决方案似乎不太可能奏效,因为它来了缩小到浏览器中的缩放级别。矢量效果:非缩放笔画也不能修复它。

有没有办法告诉 SVG 将所有路径视为 1d 几何形状,并为所有路径使用特定的笔?换句话说,我希望用于绘制所有内容的笔“随缩放而缩放”,而不是在绘制线条后缩放线条。

拜托,只有香草JS。

您可以通过运行下面的代码来测试我的意思。单击正方形并将其拖动(无触摸设备)。请注意,在某些级别的浏览器缩放中,它可能表现得很好,而不是像我所描述的那样。

发生了一些奇怪的事情。svg 元素(正方形)一开始看起来很均匀,所有边缘的宽度都相同。它仅响应鼠标移动而移动,并且该事件(可能)在鼠标移动整个(绝不是分数)屏幕像素时发生。因此,如果它以这种方式开始,人们会期望正方形移动整个屏幕像素并保持与屏幕像素对齐。

跟进...问题不在屏幕CTM上。我测试了逆真的是逆;也就是说,CTM x CTM^{-1} 是我尝试的每个值的标识。并且 matrixTransform(getScreenCTM().inverse()) 提供的值在输入中是线性的(或与浮点数允许的一样线性)。

<html>
<body>

<svg xmlns="http://www.w3.org/2000/svg" id="svgtest" 
    style = "border: 1px solid; display: block; margin-left: auto; margin-right: auto; shape-rendering: crispEdges;" 
    width = "400" height = "400" viewBox = "0 0 100 100" >
</svg>

<script>
var theSquare = {
  x : 10,
  y : 10
};

var initialSquare = {
  x : 10,
  y : 10
};

var SideLength = 10;
var initialX = 0;
var initialY = 0;
var draggingSquare = 0;

function pointInRect(p, r) {
  return p.x > r.xLeft && p.x < r.xRight && p.y > r.yTop && p.y < r.yBottom;
}

function mouseToLocal(theEvent) {
  var screenPt = svgArea.createSVGPoint();
  screenPt.x = theEvent.clientX;
  screenPt.y = theEvent.clientY;
  var svgPt = screenPt.matrixTransform(svgArea.getScreenCTM().inverse());
  return {
    x : svgPt.x,
    y : svgPt.y
  };
}
  
function doMouseDown(event) {
  var localCoord = mouseToLocal(event);
  initialX = localCoord.x;
  initialY = localCoord.y;

  var pt =  {x: initialX, y: initialY};
  var rect = {xLeft: theSquare.x, xRight: theSquare.x + SideLength,
              yTop: theSquare.y, yBottom: theSquare.y + SideLength};
  if (pointInRect(pt,rect))
    {
      draggingSquare = 1;
      initialSquare.x = theSquare.x;
      initialSquare.y = theSquare.y;
    }
  else
    draggingSquare = 0;
}

function doMouseUp(event) {
  draggingSquare = 0;
}

function doMouseMove(event) {
  if (draggingSquare == 0) 
    return;
    
  var localCoord = mouseToLocal(event);
  zeroTranslate.setTranslate(initialSquare.x + localCoord.x - initialX,
    +initialSquare.y + localCoord.y - initialY);
  
  theSquare.x = initialSquare.x + localCoord.x - initialX;
  theSquare.y = initialSquare.y + localCoord.y - initialY;
}

var svgArea = document.getElementById('svgtest');

var theSVGSquare = document.createElementNS("http://www.w3.org/2000/svg", 'path' ); 
theSVGSquare.setAttributeNS(null, "d", "M 0 0" +
  " L " + SideLength +  " 0" + 
  " L " + SideLength + " " + SideLength +
  " L 0 " +SideLength +
  " z");
theSVGSquare.setAttributeNS(null, 'style', 'fill: none; stroke: black; stroke-width: 0.5px; shape-rendering: crispEdges' );

var tforms = theSVGSquare.transform;
var zeroTranslate = svgArea.createSVGTransform();
zeroTranslate.setTranslate(initialSquare.x,initialSquare.y);
theSVGSquare.transform.baseVal.insertItemBefore(zeroTranslate,0);

svgArea.appendChild(theSVGSquare);

svgArea.addEventListener("mousedown",doMouseDown,false);
svgArea.addEventListener("mouseup",doMouseUp,false); 
svgArea.addEventListener("mousemove",doMouseMove,false);
</script>
</body>
</html>

标签: javascriptsvgzooming

解决方案


我发布这个“答案”是为了正式结束这个问题,并感谢罗伯特朗森的关注。

简而言之,我想做的事情不能在浏览器中完成,这很令人惊讶,因为我想做的只是画一条线。由于抗锯齿,使用 HTML 画布标签会产生一些模糊的东西,而 SVG 会产生抖动的线条。归根结底是浏览器没有提供以设备像素级别的精度工作所需的信息。

我在我的个人网站上发布了关于这个问题的讨论,并附有示例:

画布绘图模糊SVG 抖动


推荐阅读