Как нарисовать Xlink: HREF для холста - PullRequest
0 голосов
/ 18 ноября 2018

У меня есть несколько форм SVG, которые я хочу визуализировать.

В настоящее время я создаю объект Image для каждого svg отдельно, что приводит ко многим ненужным HTTP-запросам.

const imageEl = new Image();
imageEl.src = image.src;
// And then
ctx.drawImage(this.image, this.x, this.y, this.width, this.height);

Я хочу использовать один файл спрайта, который содержит все мои SVG'ы, и нарисовать их на холсте, как в HTML, используя <use xlink:href="SOME_URL"></use>

Как я могу это сделать?

1 Ответ

0 голосов
/ 19 ноября 2018

Несколько напоминаний:

  • Из вашего вопроса не ясно, как именно вы управляете своими активами, поэтому я добавляю это в качестве напоминания, но обратите внимание, что это самый важный момент этого ответа: Вы следует загрузить все ваши активы, только один раз, и прежде чем вы начнете использовать любой из них.
    Это означает, что вызов new Image() должен быть сделан на этапе инициализации приложения, а вызов drawImage() должен быть сделан на этапе рисования приложения, который сам вызывается в цикле anim.

    // in 'loadAssets()' called from 'init()' only once  per 'uri'
    const img = new Image();
    img.onload = thisLoaded;
    img.src = uri;
    
    // in 'draw()' called from 'anim()'
    ctx.drawImage(img, ...
    
  • Ваша проблема сводится к возможности загрузить эти ссылки из элемента image. Это то, что API Canvas2d требует в качестве источника, и это на самом деле самый большой виновник здесь. ( drawImage также принимает svg's , но я не думаю, что это имеет какое-либо значение здесь)

  • Большинство элементов svg не имеют смысла само по себе. Все координаты, которые мы используем для их определения, должны быть относительно родительского элемента viewPort viewBox . Так что практически ни один элемент не может быть окрашен только самостоятельно. Если элемент svg способен «визуализировать» отдельные элементы, то это потому, что сам этот элемент <use> содержится в элементе , который определяет, что Viewbox .

  • Изображения, загруженные из элемента image, могут не выполнять любые запросы к внешним ресурсам.
    Это означает, что динамическая генерация нескольких простых svg-документов только из <svg><use href="https://foo.bar#baz"></svg> здесь нам не поможет, # baz должна быть частью самого документа.


Теперь, когда все это прояснено, мы можем предложить несколько решений:

В приведенных ниже примерах используемое изображение SVG состоит из двух элементов, один с идентификатором rect, а другой с идентификатором circle.

Первый, который может быть наиболее очевидным, - это построить свою собственную систему.
Сначала вы можете получить svg вашей spritesheet, проанализировать из js, а затем сгенерировать svg, который вам нужен для рисования, выбрав только те элементы, которые вы хотите.

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

Итак, здесь я покажу только базовую реализацию, в которой мы можем ориентироваться только на прямые элементы (без импорта, без url (# ...) так далее.). Если вам действительно нужно что-то законченное, дайте мне знать, что я что-то написал некоторое время назад ...

function loadSvgDoc(uri) {
  return fetch(uri)
  .then(r => r.text())
  .then(markup => (new DOMParser())
      .parseFromString(markup, 'image/svg+xml')
  );
}

function loadFakeUse(doc, id) {
  const root = doc.documentElement;
  const node = doc.getElementById(id);
  if(!root || !node)
    return Promise.reject('invalid params');
  const clone =  root.cloneNode();
  clone.append(node);
  const markup = (new XMLSerializer()).serializeToString(clone);
  return new Promise((res, rej) => {
    const img = new Image();
    img.onload = e => res(img);
    img.onerror = rej;
    img.src = 'data:image/svg+xml,' + encodeURIComponent(markup);
  });
}

loadSvgDoc('https://dl.dropboxusercontent.com/s/s7aynuqx6pe1we8/fake_use.svg')
  .then(doc => 
    Promise.all([
      loadFakeUse(doc, 'rect'),
      loadFakeUse(doc, 'circle')
    ])
  )
  .then(images => {
    const ctx = canvas.getContext('2d');
    images.forEach((img, i) => 
      ctx.drawImage(img, i * 120, 0)
    );
  })
  .catch(console.error);
  
<canvas id="canvas" width="250" height="200"></canvas>

Другой метод - это своего рода CSS-хак, о котором я впервые прочитал с Lea Verou .
CSS имеет селектор :target, который позволяет стилизовать только элемент, на который указывает хеш документа.

Это означает, что вы можете установить некоторые правила в зависимости от того, на какой элемент был нацелен URL-адрес #id.

Например, мой svg имеет следующие простые правила:

 rect, circle { /* always hide */
   visibility: hidden;
 }
 :target { /* show only the targeted element */
   visibility: visible;
 }

А теперь со своей страницы все, что вам нужно сделать, это

function loadImage(url) {
  return new Promise((res, rej) => {
    const img = new Image();
    img.onload = e => res(img);
    img.onerror = rej;
    img.src = url;
  });
}

function loadImages(urls) {
  return Promise.all(urls.map(loadImage));
}

const base = 'https://dl.dropboxusercontent.com/s/s7aynuqx6pe1we8/fake_use.svg';

loadImages([
    base + '#rect',
    base + '#circle'
  ])
  .then(images => {
    const ctx = canvas.getContext('2d');
    images.forEach((img, i) =>
      ctx.drawImage(img, i * 120, 0)
    );
  })
  .catch(console.error);
<canvas id="canvas" width="250" height="200"></canvas>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...