为何SVG中图案遮罩(pattern mask)在300单位处被截断?
SVG遮罩中Pattern在viewBox偏移后截断的问题及解决方案
尝试在SVG遮罩里用Pattern实现网格效果,一开始能正常画出带遮罩的彩色圆形,但把viewBox原点改成200后,遮罩突然在300单位处截断了——移除遮罩后圆形显示完整,显然是遮罩和Pattern的坐标转换出了问题。
正常运行的代码(viewBox原点为0)
<svg width="300" height="300" viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg"> <defs> <pattern id="checked" width="2" height="2" stroke="white" stroke-linecap="square" stroke-width="1" patternTransform="rotate(45) scale(2)" patternUnits="userSpaceOnUse"> <line x1="0" y1="0" x2="0" y2="2"></line> <line x1="0" y1="0" x2="2" y2="0"></line> </pattern> <mask id="checked-mask" x="0" y="0" width="1" height="1" > <rect x="0" y="0" width="1000" height="1000" fill="url(#checked)" /> </mask> </defs> <circle cx="60" cy="60" r="50" stroke="red" mask="url(#checked-mask)" fill="green" /> <circle cx="120" cy="60" r="50" stroke="blue" mask="url(#checked-mask)" fill="magenta" /> </svg>
这段代码能正常渲染带白色网格遮罩的绿色、品红色圆形。
出现截断的代码(viewBox原点偏移至200)
<svg width="300" height="300" viewBox="200 200 300 300" xmlns="http://www.w3.org/2000/svg"> <defs> <pattern id="checked" width="2" height="2" stroke="white" stroke-linecap="square" stroke-width="1" patternTransform="rotate(45) scale(2)" patternUnits="userSpaceOnUse"> <line x1="0" y1="0" x2="0" y2="2" /> <line x1="0" y1="0" x2="2" y2="0" /> </pattern> <mask id="checked-mask" x="0" y="0" width="1" height="1" > <rect x="0" y="0" width="100%" height="100%" fill="url(#checked)" /> </mask> </defs> <circle cx="260" cy="260" r="50" stroke="red" mask="url(#checked-mask)" fill="green" /> <circle cx="320" cy="260" r="50" stroke="blue" mask="url(#checked-mask)" fill="magenta" /> <rect x='200' y='200' width='200' height='200' fill='transparent' stroke='red' /> <rect x='200' y='200' width='100' height='100' fill='transparent' stroke='blue' /> </svg>
把viewBox改成200 200 300 300并移动圆形位置后,遮罩在300单位处被截断(蓝色框标记的是截断范围,红色框验证画布实际能覆盖到400单位)。
问题根因
- Pattern的坐标系统没适配viewBox偏移:用了
patternUnits="userSpaceOnUse",意味着Pattern的坐标是基于SVG初始的用户空间(原点0,0),而不是偏移后的viewBox(原点200,200)。当遮罩里的矩形覆盖到200-500区域时,Pattern只在0-300区域有内容,300以上就没了,导致遮罩截断。 - 遮罩单位默认是元素边界框:遮罩的
x="0" y="0" width="1" height="1"默认用的是objectBoundingBox单位,也就是基于被遮罩圆形的边界框,这和Pattern的全局用户空间坐标不匹配,进一步放大了问题。
解决办法
办法1:平移Pattern适配viewBox偏移
给Pattern的patternTransform加上平移参数,匹配viewBox的偏移量,同时让遮罩里的矩形足够大,覆盖所有需要遮罩的区域:
<pattern id="checked" width="2" height="2" stroke="white" stroke-linecap="square" stroke-width="1" patternTransform="rotate(45) scale(2) translate(200, 200)" patternUnits="userSpaceOnUse"> <line x1="0" y1="0" x2="0" y2="2" /> <line x1="0" y1="0" x2="2" y2="0" /> </pattern> <mask id="checked-mask" x="0" y="0" width="1" height="1" > <rect x="200" y="200" width="1000" height="1000" fill="url(#checked)" /> </mask>
办法2:改用元素边界框的Pattern单位
把Pattern的patternUnits改成objectBoundingBox,让Pattern基于被遮罩元素的边界框重复,自动适配元素位置:
<pattern id="checked" width="0.1" height="0.1" stroke="white" stroke-linecap="square" stroke-width="0.05" patternTransform="rotate(45)" patternUnits="objectBoundingBox"> <line x1="0" y1="0" x2="0" y2="0.1" /> <line x1="0" y1="0" x2="0.1" y2="0" /> </pattern> <mask id="checked-mask" x="0" y="0" width="1" height="1" > <rect x="0" y="0" width="1" height="1" fill="url(#checked)" /> </mask>
这里Pattern的尺寸是小数(对应元素边界框的比例),不需要手动平移,每个圆形的遮罩都会自动匹配网格。
办法3:让遮罩内容用全局用户空间坐标
给遮罩加上maskContentUnits="userSpaceOnUse",让遮罩内容的坐标和主SVG保持一致,同时调整遮罩的范围到viewBox的实际区域:
<mask id="checked-mask" x="200" y="200" width="300" height="300" maskContentUnits="userSpaceOnUse" > <rect x="200" y="200" width="1000" height="1000" fill="url(#checked)" /> </mask>
这样遮罩内容的坐标和viewBox对齐,Pattern的全局坐标就能正确覆盖所有需要遮罩的区域。
内容的提问来源于stack exchange,提问作者cbradley
相关产品推荐
相关产品推荐

