Но появилась ещё одна проблема — использование will-change инициирует создание нового слоя, и чем больше ширина и высота элемента, к которому это св-во применяется, тем больше расходуется оперативной памяти для хранения слоя. Справиться с этим помогло уменьшение масштаба SVG в 10 раз. Так, например, при масштабе холста = 200% для слоя сwill-change требовалось 300 мегабайт оперативки, а после уменьшения масштаба стало нужно всего около 3 мегабайт.
Чтобы это осуществить, выставляем параметр zoom
= 0.1 и подключаем к работе методtransformToCenterViewport
, после чего применяем те же трансформации к div-элементу:
if (isPerfMode) {
this.el.classList.add('perf-mode');
// Меняем масштаб перед включением performance mode
const prevScale = this._viewportMatrix.a;
const point = this.getViewPortCenter();
const zoom = 0.1;
// Уменьшаем исходный svg, чтобы will-change тратил меньше оперативной памяти
this.transformToCenterViewport(point, zoom, true, false, true);
this.initScale = this._viewportMatrix.a;
this.createMatrix();
this.isPerfMode = true;
// Применяем трансформации к элементу-обертке
this.startPerformance();
this.transformToCenterViewport(point, prevScale, false, false, true);
}
Т.к. при переходе в режим оптимизации мы уменьшаем SVG, холст становится очень маленьким и неудобным для работы. Чтобы это исправить, применим обратное масштабирование непосредственно к div-элементу:
public startPerformance(force = false) {
...
this.isPerformance = true;
// Получаем размер области с блоками и отступ от левого угла вьюпорта
const { x, y, width, height } = this.layers.getBBox();
const initScale = this.initScale;
// Ширина и высота для обёртки и смещение по оси x и y для области с блоками
const wrapW = Math.floor(width * initScale) + 2;
const wrapH = Math.floor(height * initScale) + 2;
const layerX = -x * initScale;
const layerY = -y * initScale;
// this.wrapMatrix - матрица div-элемента с холстом
this.wrapMatrix.e = +(this._viewportMatrix.e + x * this._viewportMatrix.a);
this.wrapMatrix.f = +(this._viewportMatrix.f + y * this._viewportMatrix.d);
this.svgWrapper.style.width = wrapW + 'px';
this.svgWrapper.style.height = wrapH + 'px';
this.svgWrapper.style.transform = this.wrapMatrix.toString();
this.svgWrapper.style.willChange = 'transform';
this.layers.style.transform = `matrix(${initScale},0,0,${initScale},${layerX} ,${layerY} )`;
}
Со скоростью мы разобрались, но на этом сложности не закончились: при уменьшении масштаба холста стали пропадать детали изображения, а при увеличении оно стало размываться. На решение этой проблемы натолкнула статья о повторном растрировании композитных слоев при изменении масштаба.
После завершения масштабирования (событие о скролле), св-во will-change удаляется на 0.1 секунды и затем устанавливается заново. Это заставляет браузер повторно растрировать слой, возвращая пропавшие детали изображения:
// Добавляем 3d трансформацию, чтобы слой не был удален
this.svgWrapper.style.transform = this.wrapMatrix.toString() + ' translateZ(0)';
this.transformFrameId = requestAnimationFrame(() => {
// Устанавливаем св-во will-change для применения в следующем кадре
this.svgWrapper.style.willChange = '';
this.transformFrameId = requestAnimationFrame(() => {
this.svgWrapper.style.willChange = 'transform';
this.svgWrapper.style.transform = this.wrapMatrix.toString();
});
});
Осталось внести последний фикс — всегда отображать перемещаемый блок поверх других. В JointJS для перемещения блоков и линков по оси Z существуют методы toFront и toBack (аналог z-index в HTML). Принцип их работы заключается в сортировке элементов и перерисовке блоков и линков, это вызывает задержки.
Наши разработчики придумали следующее: блок, с которым мы взаимодействуем, временно ставится в конец дерева элементов внутри SVG (элемент с самым высоким z-index находится в конце списка) на событие mousedown
, а затем возвращается на прежнее место на событие mouseup
.
Принцип работы
Режим оптимизации можно протестировать во всех браузерах на основе Chromium (Chrome, Opera, Edge, Yandex Browser и т.п.), а также в браузере Safari. Для сценариев, содержащих от 50 блоков, функция включается автоматически. Самостоятельно включить или отключить её можно, перейдя в меню настроек сценария в правом верхнем углу:
Как только оптимизация будет включена или выключена, в верхней части окна со сценарием появится уведомление:
Ниже для сравнения представлены 2 гифки, демонстрирующие работу в редакторе с выключенным и включенным режимом оптимизации. Но поскольку всегда интереснее потестить самому, смело переходим в свой сценарий Voximplant Kit или, если ещё нет аккаунта, — на страницу регистрации.
Без оптимизации работа с холстом и его элементами выглядит примерно так (на разных компьютерах с разными мощностями результат может отличаться):
Подключаем оптимизацию и вуаля!
В итоге мы добились более плавного и быстрого перемещения и масштабирования холста, а также увеличили скорость визуализации перетаскивания блоков с подключенными линками.
Надеемся, что статья была интересной, мы будем продолжать улучшать продукт и делиться успехами и лайфхаками с вами!