Canvas.toDataURL не работает при рендеринге SVG с clip-path

Я пытаюсь преобразовать сгенерированный SVG в PNG/JPG через HTML5 Canvas. Пока что этот метод работает со всем, что я ему предлагал, за исключением одного исключения. Когда элемент в исходном SVG имеет атрибут clip-path, canvas.toDataURL() возвращает пустой URL-адрес (т. е. data;).

Это соответствующие функции, которые я использую для выполнения преобразования.

// expects svg dataurl
// returns a dataurl
async function _svgToRasterImage(url, options) {
  const image = await _svgToImageElement(url)

  const context = document.createElement('canvas').getContext('2d', {
    antialias: options.rasterAA
  })
  context.canvas.width  = options.rasterW || image.width
  context.canvas.height = options.rasterH || image.height
  context.drawImage(image,
    0, 0,          image.width,          image.height,
    0, 0, context.canvas.width, context.canvas.height
  )

  return context.canvas.toDataURL(
    options.rasterTarget,
    options.rasterQuality
  )
}

// expects svg dataurl
// returns an SVGImageElement
function _svgToImageElement(url) {
  return new Promise(async (resolve) => {
    const image = new Image()
    image.addEventListener(
      'load',
      resolve(image),
      { once: true }
    )
    image.src = url
  })
}

Вот пример проблемного SVG (Я сократил некоторые встроенные элементы, например, href="data:image/png;base64,...").

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.dev/svgjs" width="180" height="252">
  <svg width="180" height="252" x="0" y="0">
    <g>
      <rect width="162" height="226.8" x="9" y="12.600000000000001" fill-opacity="0" rx="7.2" ry="7.2" stroke-width="7.2" stroke="#00ff00"/>
    </g>
    <g>
      <ellipse rx="81" ry="113.4" cx="90" cy="126" fill-opacity="0" stroke-width="7.2" stroke="#0000ff"/>
    </g>
    <g clip-path="url(&quot;#SvgjsClipPath1009&quot;)">
      <image width="256" height="256" xlink:href="data:image/png;base64,..." transform="matrix(0.6328125,0,0,0.6328125,9,45.00000000000001)"/>
    </g>
  </svg>
  <defs>
    <clipPath id="SvgjsClipPath1009">
      <rect width="162" height="226.8" x="9" y="12.600000000000001"/>
    </clipPath>
  </defs>
</svg>

Хотя я не уверен, я уверен, что приведенный выше SVG действителен, потому что он правильно отображается в DOM как в Chrome, так и в Firefox; однако существует явная вероятность того, что я упускаю что-то очевидное, что браузер игнорирует (или исправляет для меня в этом случае)!

Итак, у вас есть SVG-разметка в файле url, который вы передаете функции? Является ли &quot: плохой копией-вставкой?

Kaiido 11.11.2022 08:36

Да, я передаю SVG dataurl, закодированный в base64, в функцию (например, data:image/svg+xml;base64,.... Я использую SVG.js для создания изображений SVG. &quot; автоматически экранируется этой библиотекой. Сначала я тоже подумал, что это странно ; однако другие встроенные ресурсы (например, шрифты) используют идентичную схему url(&quot;...&quot;) и не вызывают проблем.

shawn-mcgee 11.11.2022 08:43

В вашем загрузчике изображений есть ошибка, он не ждет загрузки изображения перед вызовом resolve(img), он устанавливает результат этого вызова в качестве обработчика события и, таким образом, вызывает его напрямую. jsfiddle.net/tmqah64o

Kaiido 11.11.2022 08:49

Я откатил ваше последнее редактирование, StackSnippets предназначены только для фактически исполняемых фрагментов кода. Этот фрагмент ничего не делает и поэтому не нуждается в StackSnippet.

Kaiido 11.11.2022 08:51

Это замечательно, стрелочные функции снова превзошли меня. Мне любопытно, почему эта ошибка возникает только при наличии clip-path, поскольку она не кажется связанной? Я тестирую ваше решение в своей реальной кодовой базе, пока мы говорим, и оно работает отлично. Что меня сбивает с толку, так это то, что я могу нормально отображать изображения без clip-path ??

shawn-mcgee 11.11.2022 09:00

Вероятно, ваш браузер смог отобразить весь svg почти синхронно, но для получения пути клипа они идут по асинхронному пути (обычно путь клипа может вызывать сетевой запрос, поскольку они могут указывать на внешний файл, хотя в <img > так нельзя).

Kaiido 11.11.2022 09:06
Создайте ползком, похожим на звездные войны, с помощью CSS и Javascript
Создайте ползком, похожим на звездные войны, с помощью CSS и Javascript
Если вы веб-разработчик (или хотите им стать), то вы наверняка гик и вам нравятся "Звездные войны". А как бы вы хотели, чтобы фоном для вашего...
учебник по Javascript
учебник по Javascript
JavaScript - это язык программирования, который обычно используется для добавления интерактивности и других динамических функций на веб-сайты. Он...
Как использовать d3.js для рисования 2D SVG-элементов в приложении Angular?
Как использовать d3.js для рисования 2D SVG-элементов в приложении Angular?
D3.js - это обширная библиотека, используемая для привязки произвольных данных к объектной модели документа (DOM). Мы разберем основные варианты...
Освоение принципов SOLID в JavaScript: Пошаговое руководство
Освоение принципов SOLID в JavaScript: Пошаговое руководство
Принцип единой ответственности (SRP): класс должен иметь только одну причину для изменения. Другими словами, у него должна быть только одна...
JavaScript - For Loop
JavaScript - For Loop
Наиболее используемая компактная форма оператора цикла.
Неделя 1 - Карточки с временной шкалой
Неделя 1 - Карточки с временной шкалой
Поскольку это была первая неделя, я решил начать с простого. Предполагалось, что у меня будет временная шкала с несколькими карточками, которые можно...
0
6
60
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Спасибо @Kaiido за помощь!

В вашем загрузчике изображений есть ошибка, он не ждет загрузки изображения перед вызовом resolve(img), он устанавливает результат этого вызова в качестве обработчика события и, таким образом, вызывает его напрямую.

Решение состоит в том, чтобы использовать функцию стрелки в обратном вызове прослушивателя событий, потому что прямой вызов resolve(image) будет немедленно выполнен, разрешая обещание до того, как изображение фактически загрузится.

function _svgToImageElement(url) {
  return new Promise(async (resolve) => {
    const image = new Image()
    image.addEventListener(
      'load',
      e => resolve(image), // <-- DO
      //   resolve(image),    <-- DON'T
      { once: true }
    )
    image.src = url
  })
}

Удивительно, сколько боли могут причинить 3 недостающих персонажа.

Другие вопросы по теме