Маски (mask) в SVG

Векторная графика SVG поддерживает систему масок, позволяющую управлять видимостью элементов через полупрозрачные области, градиенты и сложные формы. Маска в SVG — это слой, который определяет, какие части объекта остаются видимыми, а какие скрываются, используя значения яркости или альфа-канала.

Механизм маскирования особенно важен при построении визуализаций в D3.js, где требуется динамическое управление отображением данных, плавные переходы и визуальные эффекты без растровой обработки.


Принцип работы SVG-маски

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 предоставляет два ключевых атрибута, определяющих поведение маски:

maskUnits

Определяет систему координат для самой маски:

  • userSpaceOnUse — координаты совпадают с SVG-окном
  • objectBoundingBox — координаты нормализованы (0–1)

Пример:

<mask id="m1" maskUnits="objectBoundingBox">

В режиме objectBoundingBox все значения масштабируются относительно объекта, к которому применяется маска.


maskContentUnits

Определяет систему координат внутри содержимого маски:

  • userSpaceOnUse — абсолютные координаты SVG
  • objectBoundingBox — относительные координаты

Эти настройки критически важны при работе с динамическими графиками, где размеры элементов меняются.


Создание масок через D3.js

Работа с масками в D3.js строится через манипуляции DOM-элементами SVG.

Создание контейнера defs

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)");

Отличие mask от clipPath

clipPath

  • работает только с геометрией
  • не учитывает прозрачность
  • быстрее в рендеринге

mask

  • учитывает яркость и альфа-канал
  • позволяет градиенты и мягкие переходы
  • поддерживает сложные визуальные эффекты

Пример градиентной маски:

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 использует правило:

итоговая прозрачность = исходная прозрачность × значение маски

Если маска содержит полупрозрачные области, итоговый результат становится плавно исчезающим.

Это позволяет реализовать:

  • мягкие fade-out эффекты
  • виньетки
  • затемнение областей
  • выделение фокуса

Динамическое управление масками в D3.js

Одним из ключевых преимуществ 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.


Частые ошибки при работе с mask

Отсутствие fill у элементов маски

Элементы без явно заданного fill могут не интерпретироваться корректно.

Использование stroke вместо fill

Маска учитывает заливку, а не обводку. Поэтому:

  • fill="white" работает корректно
  • stroke="white" не даёт ожидаемого результата

Несовпадение координат

При использовании objectBoundingBox маска может неожиданно масштабироваться:

  • круг становится эллипсом
  • смещения зависят от размера элемента

Маски и интерактивные визуализации

Маски часто применяются в интерактивных сценариях:

  • подсветка выбранных данных
  • затемнение неактивных областей
  • создание «прожектора» (spotlight effect)

Пример эффекта прожектора

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);
});

Комбинирование mask с фильтрами SVG

Маски могут использоваться вместе с фильтрами:

  • blur + mask → эффект глубины
  • drop-shadow + mask → выделение объекта
  • brightness + mask → акцентирование области

Такое комбинирование часто используется в сложных визуализациях данных, где требуется управление вниманием пользователя к определённым сегментам графика.