PerfectVector
Автор: Irene Kim

Как исключить прозрачные области SVG из кликов Konva

Исключите прозрачные области SVG из кликов Konva. Повторите проверку попаданий через кеш, испытайте отверстия и фон и обновите карту после замены источника.

11 мин чтения
На этой странице

Чтобы прозрачные области Konva.Image игнорировали клики, дождитесь декодирования исходного изображения, вызовите imageNode.cache(), затем imageNode.drawHitFromCache(0) и перерисуйте слой. После этого альфа-канал кешированной сцены, то есть непрозрачность пикселей, определяет, какие пиксели участвуют в проверке попаданий.

Обычный узел изображения может получать события по всему прямоугольнику, включая пустое отверстие SVG. Учебник Konva Image Events показывает альтернативу по альфа-каналу. Здесь мы проверяем собственный SVG с отверстием над отдельно кликабельным фоном: у отверстия, переставшего перехватывать клики изображения, есть настоящий нижний объект, который их получит.

Это меняет область взаимодействия на canvas. Способ не создаёт из SVG редактируемые узлы Konva.Path и не меняет правила DOM SVG pointer-events.

Отделите изображение от области попаданий

Область попаданий — область, по которой Konva выбирает цель события. Её форма может отличаться от видимого рисунка. В одних приложениях широкая прямоугольная цель намеренна, в других прозрачные отверстия должны позволять выбрать нижний объект.

Нужное взаимодействиеОтправная точка
Выбирать целое изображение из любой точки прямоугольникаСтандартная обработка попаданий Image
Пропускать прозрачные пиксели к нижнему узлуКешировать изображение и строить область попаданий по альфа-каналу
Редактировать отдельные векторные области или назначать им независимые действияВыбрать отдельные контуры Konva и определить поведение
Управлять целями указателя внутри встроенного DOM SVGИспользовать отдельное руководство по SVG pointer-events

Актуальный Shape API принимает целое alphaThreshold от 0 до 255, по умолчанию 0. Передавайте число, а не функцию обратного вызова. Это отличается от визуального фильтра Threshold. В примере используется 0 с полностью непрозрачной внутренней областью и полностью прозрачными отверстиями; он не определяет лучший порог для мягких теней или сглаженных краёв.

1. Сохраните два полных исходника SVG

Создайте пустую папку. Сохраните эту собственную эмблему с отверстием как hollow.svg:

<svg xmlns="http://www.w3.org/2000/svg" width="240" height="180" viewBox="0 0 240 180">
  <path fill="#315d6f" fill-rule="evenodd"
        d="M20 30 L180 20 L215 60 L195 150 L35 160 L10 110 Z
           M80 65 L145 55 L160 110 L95 125 Z"/>
</svg>

Внутренний подконтур создаёт отверстие по правилу заливки even-odd. Сохраните ту же внешнюю фигуру без внутреннего подконтура как filled.svg:

<svg xmlns="http://www.w3.org/2000/svg" width="240" height="180" viewBox="0 0 240 180">
  <path fill="#315d6f" d="M20 30 L180 20 L215 60 L195 150 L35 160 L10 110 Z"/>
</svg>

Второй файл — контроль замены источника. Он закрашивает ранее пустую точку. Ни один файл не содержит белого фонового прямоугольника или встроенного растра.

2. Загрузите, кешируйте и записывайте настоящие цели

Пример использует Konva 10.9.0. Сохраните эти три файла рядом с обоими SVG. HTML загружает закреплённую браузерную сборку; в контролируемом проекте ту же сборку можно раздавать локально.

Сохраните reader.html:

<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>Konva transparent image hit comparison</title>
<link rel="stylesheet" href="reader.css">
<main>
<h1>Transparent artwork: which node gets the click?</h1>
<p class="banner"><strong id="version">Loading Konva…</strong>. Original same-origin SVGs, actual Konva.Image nodes, and a separately clickable background. These are canvas hit pixels, not editable SVG path nodes. Desktop click test only; no mobile or accessibility audit.</p>
<div class="grid">
<section class="panel"><h2>1. Default rectangular hit</h2><div class="stage" id="default"></div><p>The hollow image still catches its transparent center and corner.</p><pre class="log" id="default-log"></pre></section>
<section class="panel"><h2>2. Cached alpha hit region</h2><div class="stage" id="alpha"></div><p>cache() then drawHitFromCache(0): the center and corner reach the background.</p><pre class="log" id="alpha-log"></pre></section>
<section class="panel"><h2>3. Opaque image fill</h2><div class="stage" id="fill"></div><p>A white fill behind this image is part of its cached scene. Its formerly empty pixels receive image hits.</p><pre class="log" id="fill-log"></pre></section>
<section class="panel"><h2>4. Replaced source, rebuilt cache</h2><div class="stage" id="updated"></div><p>The original hole is painted in filled.svg. Clear, replace, cache, and rebuild the hit region.</p><pre class="log" id="updated-log"></pre></section>
</div>
<p>Click these stage coordinates in each panel: solid (125,110), hole (195,130), transparent corner (305,55), and outside the image (20,20). The corner is inside the image viewport but outside its painted shape. Logs report actual click targets.</p>
 
</main>
<script src="https://unpkg.com/konva@10.9.0/konva.min.js"></script>
<script type="module" src="reader.js"></script>
</html>

Сохраните reader.css:

body { margin: 32px; font: 16px/1.55 Arial, sans-serif; color: #183a42; background: #edf2ef; }
main { max-width: 1080px; margin: auto; }
h1 { font-size: 32px; margin-bottom: 12px; }
h2 { margin: 0 0 12px; font-size: 22px; }
p { margin: 10px 0; }
.banner { background: #dce8e4; padding: 18px; border-left: 5px solid #315d6f; }
.grid { display: grid; grid-template-columns: repeat(2, minmax(460px, 1fr)); gap: 20px; margin: 24px 0; }
.panel { background: white; padding: 20px; border: 1px solid #d1dbd5; border-radius: 12px; }
.stage { width: 420px; height: 260px; }
pre { white-space: pre-wrap; overflow-wrap: anywhere; background: #f8faf7; padding: 12px; }
table { border-collapse: collapse; width: 100%; margin-top: 20px; }
th, td { border-bottom: 1px solid #c9d5ce; padding: 10px; text-align: left; }
.log { min-height: 88px; margin-bottom: 0; font-size: 13px; }

Сохраните reader.js:

const source = new Image();
source.src = './hollow.svg';
await source.decode();
const replacement = new Image();
replacement.src = './filled.svg';
await replacement.decode();
 
const clicks = [];
const scenes = [];
function makeScene(id, mode) {
  const stage = new Konva.Stage({ container: id, width: 420, height: 260 });
  const layer = new Konva.Layer();
  stage.add(layer);
  const background = new Konva.Rect({
    id: `${id}-background`, width: 420, height: 260, fill: '#eadfcd'
  });
  const artwork = new Konva.Image({
    id: `${id}-image`, image: source, x: 80, y: 40, width: 240, height: 180
  });
  if (mode === 'fill') artwork.fill('#ffffff');
  layer.add(background, artwork);
  if (mode !== 'default') {
    artwork.cache();
    artwork.drawHitFromCache(0);
  }
  layer.draw();
  const beforeUpdate = stage.getIntersection({ x: 195, y: 130 }).id();
  if (mode === 'update') {
    artwork.clearCache();
    artwork.image(replacement);
    artwork.cache();
    artwork.drawHitFromCache(0);
    layer.draw();
  }
  stage.on('click', (event) => {
    const point = stage.getPointerPosition();
    const record = {
      scene: id, x: point.x, y: point.y,
      target: event.target.id(), trusted: event.evt.isTrusted
    };
    clicks.push(record);
    document.getElementById(`${id}-log`).textContent +=
      `(${Math.round(point.x)}, ${Math.round(point.y)}) → ${record.target}\n`;
    document.dispatchEvent(new CustomEvent('fixture-click-recorded'));
  });
  const scene = { id, stage, artwork, beforeUpdate };
  scenes.push(scene);
  return scene;
}
makeScene('default', 'default');
makeScene('alpha', 'alpha');
makeScene('fill', 'fill');
makeScene('updated', 'update');
document.getElementById('version').textContent = `Konva ${Konva.version}`;

Раздавайте папку через HTTP вместо открытия HTML как локального файла:

python -m http.server 8000

Откройте http://localhost:8000/reader.html и дождитесь четырёх сцен. SVG 240 × 180 размещён в координатах (80,40) каждой сцены 420 × 260. Каждый фон — отдельный принимающий события Konva.Rect под изображением. Обработчик клика сцены сообщает event.target.id(): это выбранный узел Konva, а не просто DOM-элемент canvas.

await source.decode() выполняется до создания кеша. Кешируйте после готовности настоящего источника, а не во время загрузки изображения. Node API Konva описывает создание и очистку кеша.

3. Нажмите на сплошную область, отверстие, угол и фон

Проверьте четыре точки под панелями. Это координаты внутри каждой сцены, а не относительно окна браузера.

МестоКоордината сценыЧто проверяется
Сплошной рисунок125,110Непрозрачная точка глубоко внутри эмблемы
Исходное отверстие195,130Прозрачная внутренняя область hollow.svg
Прозрачный угол305,55Внутри прямоугольника изображения, но вне закрашенного силуэта
Вне изображения20,20Отдельно кликабельный фон

Следующая таблица фиксирует проверенный тип цели. Журналы включают префикс панели, например alpha-background.

КонфигурацияСплошная областьИсходное отверстиеПрозрачный уголВне изображения
Стандартное изображениеИзображениеИзображениеИзображениеФон
Кешированная область по альфа-каналуИзображениеФонФонФон
Кешированное изображение с непрозрачной белой заливкойИзображениеИзображениеИзображениеФон
Заменённый источник и перестроенный кешИзображениеИзображениеФонФон
Реальное сравнение Konva в браузере: прямоугольные попадания по умолчанию, попадания по альфа-кешу, непрозрачная заливка и замена источника с записанными ID целей
Реальные сцены Konva 10.9.0 с собственными SVG и записанными целями настольных кликов. Изображение с отверстием по альфа-каналу пропускает клики отверстия и угла к отдельному фону. Непрозрачная заливка и закрашенный новый источник меняют результат.

Браузерный тест использовал настоящие клики указателя во всех 16 точках и записывал нативный признак доверенного события, координаты сцены и ID целей. Также проверялись хеши исходных файлов, декодирование, размеры сцены и изображения, публичные результаты пересечений и нативные альфа-образцы из отрисованного canvas каждого узла изображения.

В исходном отверстии альфа узла с отверстием была 0. У контроля с непрозрачной заливкой и заменённым источником там было 255. Отдельный фон страницы не делал само изображение непрозрачным: он принадлежал другому узлу Konva ниже. Точки находятся внутри областей, вдали от сглаженных границ.

Это один тест настольного браузера с простыми собственными SVG и сценами без преобразований. Он не подтверждает мобильное и сенсорное поведение, клавиатурное взаимодействие, матрицу браузеров, прирост производительности или порог для любых мягких краёв. Повторите клики с настоящими размерами отображения и преобразованиями приложения.

4. Перестройте кеш при изменении источника

Четвёртая панель начинается с источника с отверстием и его области по альфа-каналу. Затем очищает кеш, устанавливает уже декодированную замену, снова кеширует и перестраивает область попаданий. После этой последовательности цель в исходном отверстии меняется с фона на изображение.

Кеш — снимок отрисованного состояния. После изменения отрисованных пикселей обновляйте вместе кеш сцены и область попаданий по альфа-каналу. Не считайте, что назначение другого изображения обновило существующую кешированную область. Для асинхронной замены дождитесь её декодирования до выполнения показанной последовательности.

Третья панель выделяет другую причину: artwork.fill('#ffffff') рисует непрозрачный прямоугольник позади изображения как часть того же узла. Область попаданий, созданная из кеша сцены, включает эту краску. Удалите ненужную заливку узла, затем перестройте кеш и область. Белая на вид область исходного файла тоже может быть настоящей непрозрачной краской: проверьте прозрачность до обвинения проверки попаданий.

Раздельно проверяйте ошибки чтения пикселей и выбора целей

СимптомЧто проверить сначала
Отверстие всё ещё записывает изображениеКеш создан после декодирования, область перестроена, непрозрачная заливка или фон источника не закрывают отверстие
Рисунок изменился, а клики следуют старой формеОчистить и перестроить кеш сцены и область по альфа-каналу после изменения источника
Ошибка чтения пикселей или загрязнённого canvasИсточник изображения и заголовки ответа до интерпретации результатов попадания
Всё изображение перестало получать событияlistening, видимость, порядок слоёв и наличие верхнего узла
Отверстие ведёт к сцене, а не нижнему объектуПод точкой действительно есть нужный принимающий события объект
Клики canvas работают, клавиатура — нетКлавиатурное управление и семантика проектируются отдельно; альфа-пиксели попаданий их не предоставляют

Образец раздаёт оба SVG с того же источника, что и страницу. Для чтения пикселей изображений другого источника нужен незагрязнённый canvas. Руководство Konva по загрязнённому canvas объясняет использование crossOrigin вместе с сервером, разрешающим запрос CORS. Одно crossOrigin = 'anonymous' не даёт доступа без разрешения сервера. В статье не запускался случай ошибки другого источника.

Изменение CSS pointer-events внешнего canvas не выбирает пиксели внутри Konva.Image, попадающие в его внутренние узлы. Отделяйте цели canvas от целей указателя DOM SVG. Сохраните также оригинал SVG: кешированная карта — пиксельные данные взаимодействия, а не замена векторного документа.

Отделяйте восстановление рисунка от подключения событий

Если приложению нужны лишь размещение изображения и попадания по альфа-каналу, подходящего прозрачного растра может быть достаточно. Если остался только PNG или JPG и нужна также редактируемая геометрия, процесс изображения в вектор PerfectVector поможет восстановить кандидат SVG. Проверьте силуэт и отверстия, затем выберите представление Konva и реализуйте события. Здесь использованы собственные исходные SVG без преобразования PerfectVector.

Векторизация не устанавливает обработчики, не создаёт доступные элементы управления, не выбирает цели кликов и не синхронизирует кеши. Если оригинал SVG уже есть, исправляйте код взаимодействия напрямую. Если внутри файла неожиданно обнаруживается полное прямоугольное изображение, выполните проверку встроенного растра SVG, прежде чем считать содержимое отдельными редактируемыми контурами.

Частые вопросы

Почему прозрачные пиксели изображения Konva получают клики? Стандартный узел Image может использовать прямоугольник для попаданий. После готовности источника кешируйте его, примените drawHitFromCache с подходящим числовым порогом, перерисуйте и проверьте настоящие цели.

Какой аргумент принимает drawHitFromCache? Текущий API принимает необязательный целочисленный порог альфа-канала от 0 до 255, по умолчанию 0. Проверенный пример передаёт 0. Он не передаёт функцию обратного вызова и не использует визуальный фильтр Threshold.

Почему белая заливка снова делает отверстие SVG кликабельным? Непрозрачная заливка узла Image входит в кешированную сцену. Альфа кеша включает эту краску. Удалите ненужную заливку и перестройте кеш сцены и область попаданий.

Делает ли область по альфа-каналу контуры SVG независимо редактируемыми? Нет. Рисунок остаётся одним узлом Image. Если нужны отдельные векторные области, используйте подходящие отдельные узлы Path и явную модель сцены.

Источники

  1. Konva: Image Events — Стандартные попадания изображения и создание области из кешированного рисунка.
  2. Konva: Shape API — Текущий числовой alphaThreshold и его диапазон.
  3. Konva: Node API — Создание и очистка кеша, цели событий и публичные методы узлов.
  4. Konva: Руководство по загрязнённому canvas — Ограничения источников, CORS и ошибки чтения пикселей.
  5. Konva: SVG на canvas — Отрисовка целого изображения и отдельно построенные векторные контуры.

Если редактируемой геометрии нет, подготовьте кандидат SVG в PerfectVector, проверьте отверстия, затем отдельно испытайте нужные цели кликов Konva и обновления кеша.

Ещё из блога

PerfectVector

Начните с более чистого SVG,
который проще редактировать

Без банковской карты