Отказ от ответственности: Этот пост - половина вопроса и половина отчета о моих экспериментах при попытке найти решение.
Задача: Простой SVG-фильтр на монохромном прямоугольнике
Использование фильтра для изменения или изменения цвета монохромной фигуры в SVG довольно просто. Вот как это можно сделать:
<svg viewBox="0 0 460 130">
<defs>
<filter id="filter1">
<feColorMatrix values="0.5 0 0 0 0 0 0.5 0 0 0 0 0 0.5 0 0 0 0 0 1 0" />
</filter>
</defs>
<g transform="translate(20, 0)">
<text x="0" y="35">Original</text>
<rect x="0" y="50" width="200" height="80" fill="red" />
</g>
<g transform="translate(240, 0)">
<text x="0" y="35">Filtered</text>
<rect x="0" y="50" width="200" height="80" fill="red" filter="url(#filter1)" />
</g>
</svg>
Проблема: в Safari медленно и сбои в Safari Mobile
Маленькая проблема в том, что в Safari он работает очень медленно. В приведенном ниже фрагменте новый отфильтрованный прямоугольник добавляется в SVG каждые полсекунды. Это в конечном итоге приведет к сбою мобильного телефона Safari через 10-80 циклов, в зависимости от вашего устройства. Кроме того, это невыносимо медленно. Для сравнения, на телефоне Android начального уровня, работающем с Chrome, он работает почти всегда с 50 кадрами в секунду в начале.
var rectCount = 1;
var svgRectElem = document.getElementById('svg-rect');
var rectCountElem = document.getElementById('rect-count');
var fpsElem = document.getElementById('fps');
(function insertRect() {
window.requestAnimationFrame(function() {
var start = new Date().getTime();
var clonedSvgRectElem = svgRectElem.cloneNode();
// insert cloned rect SVG element
svgRectElem.parentNode.appendChild(clonedSvgRectElem);
rectCountElem.textContent = ++rectCount;
window.requestAnimationFrame(function() {
var fps = Math.round(100000 / (new Date().getTime() - start)) / 100;
fpsElem.textContent = fps;
window.setTimeout(insertRect, 500);
}, 100);
});
})();
<p>SVG Rect Count: <span id="rect-count">1</span></p>
<p>fps: <span id="fps"></span></p>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="512" viewBox="0,0,1024,1024">
<defs>
<filter id="filter-1">
<feColorMatrix values="0.5 0 0 0 0 0 0.5 0 0 0 0 0 0.5 0 0 0 0 0 1 0" />
</filter>
</defs>
<rect id="svg-rect" fill="red" filter="url(#filter-1)" x="0" y="0" width="1024" height="1024" />
</svg>
Фрагмент на CodePen
Возможные решения: что я придумала до сих пор
Я пробовал несколько подходов, но, как вы увидите. Как вы увидите, некоторые из них работают на Safari, у каждого есть подвох.
1. Ждите исправления командой WebKit
tl; dr Может работать, а может и нет
Это был бы наиболее удобный вариант, но если учесть, что фильтры SVG были введены 15 лет назад, я бы не стал задерживать решение этой проблемы в ближайшее время. И да, я написал отчет об ошибке.
2. Используйте filterRes
tl; dr Работает, но только до следующего выпуска Safari
В SVG 1.1 определен атрибут filterRes
. Это определяет разрешение фильтра. Значение 1 означает, что источник фильтра будет обрабатываться как один пиксель. Таким образом, вместо того, чтобы пробегать все пиксели, фильтр должен применяться только к одному пикселю. Поскольку в качестве входного изображения мы используем один цветной прямоугольник, в нашем случае можно рассматривать все изображение как один пиксель.
В нашем примере фильтр будет выглядеть так:
<filter id="filter1" filterRes="1">
<feColorMatrix values="0.5 0 0 0 0 0 0.5 0 0 0 0 0 0.5 0 0 0 0 0 1 0" />
</filter>
Фрагмент на CodePen
На самом деле это значительно повышает производительность и не происходит сбоев!
Печальная новость в том, что filterRes
был удален из стандарта SVG в версии 2.0. WebKit очень быстро адаптировал эту часть SVG 2.0 и решил удалить ее в следующей версии. Если мы полагаемся на filterRes
, та же проблема скоро появится снова.
3. Отфильтруйте маленький прямоугольник, затем увеличьте
tl; dr Не работает
Приведенный выше подход показывает, что применение фильтра в небольших регионах может значительно повысить производительность. Там мы применяем фильтр к маленькому прямоугольнику, а затем масштабируем результат до желаемого размера. Для масштабирования мы используем атрибут transform
. Если мы установим прямоугольник на 1/64 от желаемого размера, мы затем масштабируем его с помощью transform="scale(64)"
, чтобы получить окончательный размер 1024.
Фрагмент в CodePen
К сожалению, это не работает вообще. Производительность остается неизменной, и он по-прежнему падает на мобильном телефоне Safari. Интересно, что это нельзя отнести к фильтру или преобразованию в одиночку. Только в комбинации это замедляется и вылетает.
4 Отфильтруйте маленький прямоугольник, затем используйте feTile
, чтобы заполнить целевой прямоугольник
tl; dr Работает немного, но артефакты на Chrome
Это приспособление похоже на предыдущее. Сначала мы применяем фильтр к маленькому прямоугольнику, а затем используем второй фильтр с feTile
, чтобы заполнить целевой прямоугольник.
Фрагмент на CodePen
Это работает на мобильном телефоне Safari. Не так хорошо, как filterRes
, но есть некоторое улучшение.
Но для этого подхода Chrome - это вечеринка Pooper . Если вы увеличиваете или уменьшаете масштаб в Chrome, при некоторых уровнях масштабирования отображается растр. Вот как это может выглядеть (правильно будет один большой квадрат):
![enter image description here](https://i.stack.imgur.com/VOwfE.png)
5 Используйте огромные значения для ширины и высоты фильтра
tl; dr Работает, но взломает и вызывает проблемы в Internet Explorer
Это очень противоречивый подход. Установка ширины и высоты области фильтра на очень большие значения должна сделать фильтр намного медленнее, потому что придется обрабатывать миллиарды пикселей. Но на самом деле все наоборот. Фильтр резко ускоряется в WebKit, и сбоев не происходит.
Вот как это реализовать в нашем примере:
<filter id="filter-1" x="0" y="0" width="102400" height="102400" filterUnits="userSpaceOnUse">
<feColorMatrix values="0.5 0 0 0 0 0 0.5 0 0 0 0 0 0.5 0 0 0 0 0 1 0" />
</filter>
Фрагмент в CodePen
Я думаю, что здесь происходит то, что фильтр сначала уменьшает исходную область фильтра, затем обрабатывает часть, которая будет видима в SVG viewBox (которая является маленькой областью после уменьшения масштаба), а затем снова увеличивает эту область. И это кажется быстрым.
К сожалению это довольно хак, и мы не знаем, как будущие версии браузеров будут обрабатывать SVG с такими большими значениями. Кроме того, это вызывает проблемы в Internet Explorer, когда область прокрутки документа становится ОГРОМНОЙ (вероятно, из-за того, что границы фильтра рассчитываются по размеру), делая прокрутку практически невозможной. И если вы выберете слишком малые значения, Internet Explorer фактически отобразит прямоугольник в размере границ фильтра.
Какое решение?
На самом деле, я не знаю. Существует простое решение с использованием filterRes
, но оно скоро истечет (оно уже не поддерживается в предварительном просмотре технологии WebKit), поэтому это не вариант. Все остальные подходы вызывают проблемы в других браузерах.
Можете ли вы вспомнить какой-либо другой подход, который я мог бы попробовать? Я не могу поверить, что кросс-браузерное использование фильтров SVG через пятнадцать лет после выпуска спецификации SVG 1.1 является таким приключением.