Несколько напоминаний:
Из вашего вопроса не ясно, как именно вы управляете своими активами, поэтому я добавляю это в качестве напоминания, но обратите внимание, что это самый важный момент этого ответа: Вы следует загрузить все ваши активы, только один раз, и прежде чем вы начнете использовать любой из них.
Это означает, что вызов 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, ...
Ваша проблема сводится к возможности загрузить эти ссылки из элемента . Это то, что API Canvas2d требует в качестве источника, и это на самом деле самый большой виновник здесь. ( drawImage также принимает svg's , но я не думаю, что это имеет какое-либо значение здесь)
Большинство элементов svg не имеют смысла само по себе. Все координаты, которые мы используем для их определения, должны быть относительно родительского элемента viewPort viewBox . Так что практически ни один элемент не может быть окрашен только самостоятельно. Если элемент svg способен «визуализировать» отдельные элементы, то это потому, что сам этот элемент <use>
содержится в элементе , который определяет, что Viewbox .
Изображения, загруженные из элемента , могут не выполнять любые запросы к внешним ресурсам.
Это означает, что динамическая генерация нескольких простых 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>