#javascript #performance #canvas #d3.js #optimization
#javascript #Производительность #холст #d3.js #оптимизация
Вопрос:
У меня есть лабиринт, и я хочу создать круг в каждой его ячейке, а затем переместить эти круги в правую часть лабиринта в виде дерева. Я использую переходы d3 и холст html5. Мой вопрос в том, есть ли какие-либо оптимизации, которые я могу сделать. Я не знаю производительности canvas, поэтому я не знаю, сколько я могу ожидать.
Для 1600 элементов (600 пикселей x 600 пикселей и размер ячейки 15 пикселей) анимация плавная. Для 3600 элементов это не так.
mazeSelection.selectAll('cell')
.data(root.descendants())
.enter()
.append('cell')
.attr('radius', 0)
.attr('cx', d => d.data.ix * cellSize 0.5 * cellSize,)
.attr('cy', d => d.data.iy * cellSize 0.5 * cellSize)
.transition().duration(2000)
.attr('radius', cellSize/2)
.transition().duration(3000)
.attr('radius', cellSize/4)
.tween('position', function(d) {
const i = d3.interpolate([this.getAttribute('cx'), this.getAttribute('cy')], [width d.x, cellSize * 0.5 d.y]);
return (t) => {
[d.cx, d.cy] = i(t);
this.setAttribute('cx', d.cx);
this.setAttribute('cy', d.cy);
};
});
context.fillStyle = "white";
let timer = d3.timer(function redraw() {
// clear maze
context.beginPath();
context.fillRect(0, 0, width * 2, height);
//drawMaze(grid, context, width, height, cellSize);
context.beginPath();
// here we must use a function to have access to "this"
mazeSelection.selectAll('cell')
.each(function (d) {
const radius = this.getAttribute('radius'),
x = this.getAttribute('cx'),
y = this.getAttribute('cy');
context.moveTo(x, y);
context.arc(x, y, radius * 0.5, 0, 2 * Math.PI);
(d.children || []).forEach(child => {
context.moveTo(x, y);
context.lineTo(child.cx, child.cy);
});
});
context.stroke();
context.fill();
});
Комментарии:
1. Вы создаете здесь элементы DOM, лучше всего избегать этого и создать некоторый список структур javascript, а затем выполнить итерацию по этому.
2. Спасибо за ответ. У меня есть 2 вопроса: 1. Несмотря на то, что я использую append, элемент не отображается в дереве DOM, когда я использую chrome dev tools. Неужели они настолько плохи? 2. Может быть, вы знаете, как применить переход без создания элементов DOM?
3. 1. вы можете создавать больше, чем в дереве DOM, инструменты Chrome dev могут показать вам только тот, который вы прикрепили к документу. 1b) да 2. перекодируйте в соответствии с моим первоначальным комментарием.
4. 1. Я понимаю. 2. Не могли бы вы привести какой-нибудь простой пример? D3 имеет удобный метод переноса при выборе, и я не знаю, как его использовать, ничего не добавляя.
Ответ №1:
Вы должны пойти по старой школе, чтобы получить максимальную производительность.
Я проигнорирую первую половину, поскольку я не уверен, что вы с ней делаете. И я предполагаю, что это такое в .each
итерации
Что вы делаете неправильно с точки зрения производительности, как указано в вашем коде ниже
// why use a let in global scope????
let timer = d3.timer(function redraw() {
context.beginPath();
context.fillRect(0, 0, width * 2, height);
context.beginPath();
// You wrote "here we must use a function to have access to "this""
// What 'this'? I assume it is the iterated cell `this`. Just use the
// cell referance, you dont have to use `this`
mazeSelection.selectAll('cell')
.each(function (d) { // dont use callbacks to iterate. For loops are much quicker
// on chrome const and let are half the speed of var
const radius = this.getAttribute('radius'), // why use getAttribute, a pointless and very CPU expensive way to get an object's property
x = this.getAttribute('cx'),
y = this.getAttribute('cy');
// why even assign the above properties to a const when you can use them directly
context.moveTo(x, y);
context.arc(x, y, radius * 0.5, 0, 2 * Math.PI);
// use a for loop and don't test for the existence of children
// by creating a new Array if they don't exist and then calling
// the array method forEach, a complete waste of CPU time
(d.children || []).forEach(child => {
context.moveTo(x, y);
context.lineTo(child.cx, child.cy);
});
});
context.stroke();
context.fill();
});
Если вы попробуете выполнить следующие итерации объектов, вы получите дополнительную производительность. Тем не менее, 3000 все еще могут быть недоступны.
var timer = d3.timer(function redraw() {
context.beginPath();
context.fillRect(0, 0, width * 2, height);
context.beginPath();
var cells = mazeSelection.selectAll('cell')
for(var i = 0; i < cells.length; i ){
var d = cell[i];
var x = d.x; // create a local referance as x amp; y may be used many times
var y = d.y;
context.moveTo(x, y);
context.arc(x, y, d.radius * 0.5, 0, 2 * Math.PI);
if(d.children){
var c = d.children;
for(var j = 0; j < c.length; j ){
context.moveTo(x, y);
context.lineTo(c[j].cx, c[j].cy);
}
}
context.stroke();
context.fill();
};
Если отображаемые вами дуги имеют примерно одинаковый размер, вы можете получить некоторое увеличение производительности, отобразив дуги на внеэкранном холсте (листе спрайтов), а затем нарисовав дуги, drawImage(spriteSheet,...
хотя в зависимости от оборудования / браузера это может быть незначительным или значительным увеличением производительности
Комментарии:
1. Спасибо за ответ! 1. Что именно не так с let в глобальной области видимости? 2. Я не думаю, что атрибут является свойством ячейки, поскольку, как вы показали, он не определен. 3. Дуги имеют одинаковый размер, но они перемещаются. Могу ли я использовать drawImage в этой ситуации?
2. 2. Хорошо, я должен просто использовать
property()
вместоattr()
в d3.3. @JulianRubin
let
вдвое быстрееvar
, чем в Chrome, и поскольку междуlet
иvar
в GS нет разницы, зачем использовать let? Выведите на консоль то, что вы повторяетеconsole.log(this)
, и узнайте, чтоthis
есть.. Да, вы можете использоватьdrawImage
для перемещения спрайтов. Вы должны использовать прямую ссылку, а не вызов функции (свойство или attr), они просто проверяют.4. @JulianRubin, это крошечная микрооптимизация (хотя все равно полезно знать). На вашу реальную проблему указал комментарий Роберта. Не используйте элементы DOM для canvas с d3. Используйте прямые объекты js. В вашем коде не должно быть no
select
, noappend
noattr
.5. @Kaiido знаете ли вы какой-нибудь способ использовать переходы d3 с объектами js? Я не знаю, как упорядочить свойства объекта для изменения с переходами. Я делаю select и attr, потому что именно так я обычно меняю значения данных с течением времени.