Векторная графика SVG поддерживает систему масок, позволяющую управлять видимостью элементов через полупрозрачные области, градиенты и сложные формы. Маска в SVG — это слой, который определяет, какие части объекта остаются видимыми, а какие скрываются, используя значения яркости или альфа-канала.
Механизм маскирования особенно важен при построении визуализаций в D3.js, где требуется динамическое управление отображением данных, плавные переходы и визуальные эффекты без растровой обработки.
SVG-маска определяется через элемент <mask> внутри
блока <defs>. Она накладывается на целевой элемент и
влияет на его видимость по следующему принципу:
В отличие от clipPath, который работает только с
геометрией, маска учитывает яркость и альфа-канал, что делает её более
гибким инструментом.
<svg width="400" height="200">
<defs>
<mask id="simpleMask">
<rect width="100%" height="100%" fill="white"></rect>
<circle cx="150" cy="100" r="50" fill="black"></circle>
</mask>
</defs>
<rect width="100%" height="100%" fill="steelblue"
mask="url(#simpleMask)"></rect>
</svg>
Здесь прямоугольник остаётся видимым везде, кроме области круга.
SVG предоставляет два ключевых атрибута, определяющих поведение маски:
Определяет систему координат для самой маски:
userSpaceOnUse — координаты совпадают с SVG-окномobjectBoundingBox — координаты нормализованы (0–1)Пример:
<mask id="m1" maskUnits="objectBoundingBox">
В режиме objectBoundingBox все значения масштабируются
относительно объекта, к которому применяется маска.
Определяет систему координат внутри содержимого маски:
userSpaceOnUse — абсолютные координаты SVGobjectBoundingBox — относительные координатыЭти настройки критически важны при работе с динамическими графиками, где размеры элементов меняются.
Работа с масками в D3.js строится через манипуляции DOM-элементами SVG.
const svg = d3.select("svg");
const defs = svg.append("defs");
const mask = defs.append("mask")
.attr("id", "mask-example");
mask.append("rect")
.attr("width", 400)
.attr("height", 200)
.attr("fill", "white");
mask.append("circle")
.attr("cx", 200)
.attr("cy", 100)
.attr("r", 60)
.attr("fill", "black");
svg.append("rect")
.attr("width", 400)
.attr("height", 200)
.attr("fill", "orange")
.attr("mask", "url(#mask-example)");
Пример градиентной маски:
const gradient = defs.append("linearGradient")
.attr("id", "fade");
gradient.append("stop")
.attr("offset", "0%")
.attr("stop-color", "black");
gradient.append("stop")
.attr("offset", "100%")
.attr("stop-color", "white");
mask.append("rect")
.attr("width", 400)
.attr("height", 200)
.attr("fill", "url(#fade)");
SVG использует правило:
итоговая прозрачность = исходная прозрачность × значение маски
Если маска содержит полупрозрачные области, итоговый результат становится плавно исчезающим.
Это позволяет реализовать:
Одним из ключевых преимуществ D3.js является возможность изменять маски в реальном времени.
const maskCircle = mask.append("circle")
.attr("cx", 200)
.attr("cy", 100)
.attr("r", 10)
.attr("fill", "black");
maskCircle.transition()
.duration(2000)
.attr("r", 100);
Такое расширение маски создаёт эффект «раскрытия» изображения.
Маски часто применяются не как декоративный элемент, а как способ отображения данных.
const progressMask = defs.append("mask")
.attr("id", "progressMask");
progressMask.append("rect")
.attr("width", 0)
.attr("height", 20)
.attr("fill", "white");
svg.append("rect")
.attr("width", 300)
.attr("height", 20)
.attr("fill", "green")
.attr("mask", "url(#progressMask)");
Далее ширина прямоугольника в маске может динамически изменяться в зависимости от данных.
Градиентные маски позволяют создавать плавные переходы прозрачности.
mask.append("rect")
.attr("width", 400)
.attr("height", 200)
.attr("fill", "url(#fade)");
const radial = defs.append("radialGradient")
.attr("id", "radialFade");
radial.append("stop")
.attr("offset", "0%")
.attr("stop-color", "white");
radial.append("stop")
.attr("offset", "100%")
.attr("stop-color", "black");
Такие маски используются для фокусировки внимания на центре диаграммы.
Маска может применяться не только к одному элементу, но и к группе
<g>.
svg.append("g")
.attr("mask", "url(#mask-example)")
.selectAll("circle")
.data([10, 20, 30])
.enter()
.append("circle")
.attr("cx", (d, i) => i * 80 + 50)
.attr("cy", 100)
.attr("r", d => d);
Это позволяет управлять видимостью целых визуализационных слоёв.
Использование масок связано с дополнительной нагрузкой на браузерный рендеринг:
При работе с большими наборами данных в D3.js часто предпочтительнее
ограничивать область применения масок или заменять их
clipPath.
Элементы без явно заданного fill могут не
интерпретироваться корректно.
Маска учитывает заливку, а не обводку. Поэтому:
fill="white" работает корректноstroke="white" не даёт ожидаемого результатаПри использовании objectBoundingBox маска может
неожиданно масштабироваться:
Маски часто применяются в интерактивных сценариях:
const spotlight = mask.append("circle")
.attr("cx", 200)
.attr("cy", 100)
.attr("r", 80)
.attr("fill", "white");
svg.on("mousemove", (event) => {
const [x, y] = d3.pointer(event);
spotlight
.attr("cx", x)
.attr("cy", y);
});
Маски могут использоваться вместе с фильтрами:
Такое комбинирование часто используется в сложных визуализациях данных, где требуется управление вниманием пользователя к определённым сегментам графика.