Статьи

Как перевести из DOM в SVG координаты и обратно

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

У SVG есть своя собственная система координат. Он определяется с помощью атрибута viewbox , например, viewbox="0 0 800 600" который устанавливает ширину 800 единиц и высоту 600 единиц, начиная с (0, 0). Если вы разместите этот SVG в области 800 × 600 пикселей, каждый блок SVG будет отображаться непосредственно в пиксел экрана.

Однако красота векторных изображений заключается в том, что они могут быть масштабированы до любого размера. Ваш SVG может быть масштабирован в пространстве 400 × 300 или даже растянут до неузнаваемости в пространстве 100 × 1200. Добавление дополнительных элементов в SVG становится сложным, если вы не знаете, где их разместить.

(Системы координат SVG могут сбивать с толку — параметры области просмотра Сары Соуэдан , viewBox и preserveAspectRatio описывают параметры.)

Простая разделенная синергия SVG

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

SVG, встроенные в страницу (а не изображение или фон CSS), становятся частью DOM и могут управляться аналогично другим элементам. Например, дан базовый SVG с одним кружком:

 <svg id="mysvg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 600" preserveAspectRatio="xMidYMid meet"> <circle id="mycircle" cx="400" cy="300" r="50" /> <svg> 

мы можем применить CSS эффекты:

 circle { stroke-width: 5; stroke: #f00; fill: #ff0; } circle:hover { stroke: #090; fill: #fff; } 

и присоедините обработчики событий, чтобы изменить их атрибуты:

 var mycircle = document.getElementById('mycircle'); mycircle.addEventListener('click', function(e) { console.log('circle clicked - enlarging'); mycircle.setAttributeNS(null, 'r', 60); }, false); 

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

Перевод координат SVG в DOM

Что, если мы хотим наложить элемент DOM поверх элемента SVG, например, меню или информационного поля на карте? Опять же, поскольку наши SVG-элементы, встроенные в HTML, являются частью DOM, мы можем использовать потрясающий метод getBoundingClientRect () для возврата всех измерений за один вызов. Откройте консоль в приведенном выше примере, чтобы показать новые атрибуты выбранного круга после увеличения радиуса.

Element.getBoundingClientRect() поддерживается во всех браузерах и возвращает объект DOMrect со следующими свойствами в пиксельных измерениях:

  • .x и .left — x-координата относительно начала координат области просмотра левой стороны элемента
  • .right — X-координата относительно начала координат области просмотра правой стороны элемента
  • .y и .top — y-координата относительно начала координат области просмотра верхней стороны элемента
  • .bottom — y-координата относительно начала координат области просмотра нижней стороны элемента
  • .width — ширина элемента (не поддерживается в IE8 и ниже, но идентична .right минус .left )
  • .height — высота элемента (не поддерживается в IE8 и ниже, но идентична .bottom минус .top )

Все координаты относятся к окну просмотра браузера и поэтому будут меняться при прокрутке страницы. Абсолютное местоположение на странице можно рассчитать, добавив window.scrollX к .left и window.scrollY к .top .

Перевод координат DOM в SVG

Это сложная часть. Предположим, вы щелкнули SVG и хотите создать или расположить элемент SVG в этой точке. Объект обработчика событий даст вам пиксельные координаты DOM .clientX и .clientY но они должны быть переведены в единицы SVG.

Соблазнительно думать, что вы можете вычислить координаты x и y точки SVG, применяя коэффициент умножения к местоположению пикселя. Например, если SVG шириной 1000 единиц помещен в контейнер шириной 500 пикселей, вы можете умножить любую координату x курсора на два, чтобы получить местоположение SVG. Это редко работает!…

  • Нет никакой гарантии, что SVG точно поместится в ваш контейнер.
  • Если размеры страницы или элемента изменяются — возможно, в ответ на изменение размера браузера пользователем — коэффициенты x и y должны быть пересчитаны.
  • SVG может быть преобразован в 2D или 3D пространстве.
  • Даже если вы преодолеете эти препятствия, это никогда не будет работать так, как вы ожидаете. Часто есть предел погрешности.

К счастью, SVG предоставляют свои собственные механизмы матричного факторинга для перевода координат. Первым шагом является создание точки в SVG с помощью createSVGPoint() и передача на нашем экране координат x и y:

 var svg = document.getElementById('mysvg'), pt = svg.createSVGPoint(); pt.x = 100; pt.y = 200; 

Затем мы можем применить матричное преобразование. Эта матрица создается из инверсии метода SVG (недостаточно документирован!) .getScreenCTM() который отображает единицы SVG в экранные координаты:

 var svgP = pt.matrixTransform(svg.getScreenCTM().inverse()); 

svgP теперь есть свойства .x и .y которые предоставляют координаты SVG.

Поэтому мы можем поместить кружок в точку, щелкнувшую на холсте SVG:

 var svg = document.getElementById('mysvg'), NS = svg.getAttribute('xmlns'); svg.addEventListener('click', function(e) { var pt = svg.createSVGPoint(), svgP, circle; pt.x = e.clientX; pt.y = e.clientY; svgP = pt.matrixTransform(svg.getScreenCTM().inverse()); circle = document.createElementNS(NS, 'circle'); circle.setAttributeNS(null, 'cx', svgP.x); circle.setAttributeNS(null, 'cy', svgP.y); circle.setAttributeNS(null, 'r', 10); svg.appendChild(circle); }, false); 

Методы createElementNS() и setAttributeNS() идентичны стандартным методам DOM createElement() и setAttribute() за исключением того, что они задают URI пространства имен XML . Другими словами, они действуют на SVG, а не на HTML. setAttributeNS() может быть передан пустой URI пространства имен, потому что он напрямую манипулирует элементом SVG.

DOM для преобразованных координат элементов SVG

Есть дальнейшее осложнение. Что если мы нажмем на элемент SVG, который каким-то образом преобразован? Он может быть масштабирован, повернут или перекошен, что повлияет на полученную координату SVG. Например, этот слой <g> в 4 раза больше, чем стандартная единица, поэтому координаты будут в четыре раза меньше координат SVG:

 <g id="local" transform="scale(4)"> <rect x="50" y="50" width="100" height="100" /> </g> 

Результирующий прямоугольник имеет размер 400 единиц в позиции 200, 200.

К счастью, .getScreenCTM() может использоваться на любом элементе SVG, и результирующая матрица учитывает все преобразования. Поэтому мы можем создать простую svgPoint перевода svgPoint :

 var svg = document.getElementById('mysvg'), local = svg.getElementById('local'); console.log( svgPoint(svg, 10, 10) ); // returns x, y console.log( svgPoint(local, 10, 10) ); // = x/4, y/4 // translate page to SVG co-ordinate function svgPoint(element, x, y) { var pt = svg.createSVGPoint(); pt.x = x; pt.y = y; return pt.matrixTransform(element.getScreenCTM().inverse()); } 

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

Вы можете упростить жизнь, обеспечив, чтобы все юниты SVG отображались непосредственно на координаты страницы, но в какой-то момент ваш клиент попросит, чтобы это изображение было «всего на несколько пикселей больше», и ваша система сломается. Переводите между системами координат сейчас, и вам больше не придется беспокоиться!