Как будешь искать причину замедления скролла таблицы?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Диагностика и оптимизация замедленного скролла таблицы
Замедленный скролл таблицы — очень распространённая проблема с производительностью. Это не случайность, а результат конкретных причин. Вот мой алгоритм диагностики и решения.
Шаг 1: Использование DevTools Performance
Первое, что я делаю — открываю Chrome DevTools и записываю профиль производительности:
// 1. Открыть Chrome DevTools (F12)
// 2. Перейти на вкладку Performance
// 3. Нажать Record
// 4. Скроллить таблицу 3-5 секунд
// 5. Нажать Stop
// На графике FPS (кадры в секунду) должны быть > 60 fps
// Если < 30 fps - есть проблема производительности
// В главной панели я ищу:
// - "Rendering" (красные полосы = проблема)
// - "Layout" (перепросчёты позиций)
// - "Paint" (перерисовка элементов)
// - "Composite" (композиция слоёв)
Что смотреть на графике:
- Жёлтая полоса = JavaScript выполнение
- Зелёная полоса = Rendering (Layout + Paint)
- Фиолетовая полоса = Composite
Шаг 2: Выявление основной причины
Проблема 1: Тяжелый JavaScript
// ПЛОХО - JavaScript блокирует скролл
function handleScroll() {
const visibleRows = calculateVisibleRows(); // Дорогая операция
const filteredData = filterData(); // Еще одна дорогая операция
const sortedData = sortData(); // И ещё одна
render(sortedData);
}
tableElement.addEventListener('scroll', handleScroll);
Решение: Дроссельное ограничение (throttle)
// ХОРОШО - ограничить частоту вызовов
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
const handleScroll = throttle(() => {
console.log('Scroll event');
}, 100); // Максимум один раз в 100ms
tableElement.addEventListener('scroll', handleScroll);
Проблема 2: Дорогие DOM операции (Layout Thrashing)
// ПЛОХО - часто читаем и пишем в DOM (вызывает рассчитать layout)
function updateRows() {
for (let i = 0; i < rows.length; i++) {
rows[i].style.top = (i * 40) + 'px'; // Запись
const height = rows[i].offsetHeight; // Чтение - вызывает recalc!
}
}
Решение: Батчировать чтения и записи
// ХОРОШО - сначала прочитать всё, потом написать
function updateRows() {
// Фаза чтения
const heights = rows.map(r => r.offsetHeight);
// Фаза записи
rows.forEach((row, i) => {
row.style.top = (i * 40) + 'px';
});
}
// Или использовать requestAnimationFrame
function updateRowsOptimized() {
requestAnimationFrame(() => {
rows.forEach((row, i) => {
row.style.top = (i * 40) + 'px';
});
});
}
Шаг 3: React-специфичные проблемы
Проблема: Ненужные re-renders
// ПЛОХО - каждый скролл вызывает re-render всех строк
function TableComponent({ rows }) {
return (
<div onScroll={handleScroll}>
{rows.map(row => (
<TableRow key={row.id} row={row} /> // Вся таблица ре-рендерится!
))}
</div>
);
}
// ХОРОШО - мемоизировать строки
const TableRow = React.memo(({ row }) => (
<div className="row">{row.name}</div>
));
function TableComponent({ rows }) {
const handleScroll = useCallback(throttle(() => {
// ...
}, 100), []);
return (
<div onScroll={handleScroll}>
{rows.map(row => (
<TableRow key={row.id} row={row} />
))}
</div>
);
}
Проблема: Виртуализация не используется
// ПЛОХО - рендерить 1000 строк сразу
function LargeTable({ items }) {
return (
<div>
{items.map(item => (
<div key={item.id} style={{height: '40px'}}>
{item.name}
</div>
))}
</div>
);
}
// ХОРОШО - использовать виртуализацию
import { FixedSizeList } from 'react-window';
function LargeTable({ items }) {
return (
<FixedSizeList
height={600} // Видимая высота
itemCount={items.length}
itemSize={40} // Высота каждой строки
width="100%"
>
{({ index, style }) => (
<div style={style}>
{items[index].name}
</div>
)}
</FixedSizeList>
);
}
Шаг 4: CSS-проблемы (Painting)
Проблема: Дорогие CSS свойства
/* ПЛОХО - дорогие для браузера при скролле */
.row {
box-shadow: 0 4px 6px rgba(0,0,0,0.1); /* Может вызывать repaint */
border-radius: 8px; /* Может замедлить */
filter: blur(5px); /* ОЧЕНЬ дорого! */
}
/* ХОРОШО - оптимизированные свойства */
.row {
background-color: white;
border: 1px solid #ddd;
/* box-shadow и filter только для выделенных строк */
}
.row:hover {
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
Решение: Использовать will-change и contain
/* will-change подсказывает браузеру, что элемент изменится */
.row {
will-change: transform;
contain: layout style paint; /* Изолировать элемент для оптимизации */
}
/* Трансформация работает через GPU (быстро) */
.row {
transform: translateZ(0); /* Включить GPU ускорение */
}
Шаг 5: Network и Data Binding
Проблема: Скролл вызывает API запросы
// ПЛОХО - каждый скролл = новый запрос
function useTableScroll(rows) {
useEffect(() => {
const handleScroll = async () => {
const newData = await fetch('/api/table?offset=...');
setRows([...rows, ...newData]); // Причина замедления!
};
element.addEventListener('scroll', handleScroll);
}, [rows]);
}
// ХОРОШО - ограничить и дебаунс
function useTableScroll() {
const [rows, setRows] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const loadMore = useCallback(
debounce(async (offset) => {
if (isLoading) return;
setIsLoading(true);
const data = await fetch(`/api/table?offset=${offset}`);
setRows(prev => [...prev, ...data]);
setIsLoading(false);
}, 300),
[isLoading]
);
return { rows, loadMore };
}
Полный Checklist Диагностики
// 1. Performance Timeline - ищем где теряются FPS
const marker = performance.mark('scroll-start');
// ... скролл ...
performance.mark('scroll-end');
performance.measure('scroll', 'scroll-start', 'scroll-end');
console.log(performance.getEntriesByType('measure'));
// 2. Подсчитать re-renders в React
function useRenderCount(name) {
const renders = useRef(0);
useEffect(() => {
renders.current++;
console.log(`${name} rendered ${renders.current} times`);
});
}
// 3. Профилировать функции
function slowFunction() {
console.time('slowFunc');
// ... код ...
console.timeEnd('slowFunc');
}
// 4. Использовать DevTools Rendering Stats
// Chrome DevTools -> More -> Rendering -> Paint flashing
// Зелёные вспышки = покраска, должны быть только при скролле!
// 5. Проверить Composite Layers
// DevTools -> More -> Layers
// Должны быть слои, которые не пересчитываются при скролле
Типичные решения
| Проблема | Решение |
|---|---|
| JavaScript блокирует скролл | throttle/debounce, requestAnimationFrame |
| Layout thrashing | Батчировать reads/writes |
| Ненужные re-renders | React.memo, useMemo, useCallback |
| Рендер 1000+ строк | Виртуализация (react-window) |
| Дорогой CSS | will-change, contain, GPU acceleration |
| API запросы при скролле | Дебаунс, infinite scroll pagination |
Инструменты которые я использую
// react-window - виртуализация
import { FixedSizeList } from 'react-window';
// lodash - throttle/debounce
import { throttle, debounce } from 'lodash';
// Chrome DevTools Performance
// Firefox DevTools Inspector
// Lighthouse Performance audit
// Perftools.dev для визуализации профиля
performance.measureUserAgentSpecificMemory();
Итог
Мой алгоритм поиска причины замедления скролла:
- Performance Timeline - определить, что именно медленное
- React DevTools Profiler - количество re-renders
- Chrome DevTools Rendering - paint flashing и слои
- Code review - throttle, memo, виртуализация
- Профилирование кода - найти узкие места
- Применить решение - обычно это комбинация техник
- Проверить FPS - должно быть > 60 FPS
В 95% случаев проблема одна из: дорогие re-renders, отсутствие виртуализации, layout thrashing или неправильный дроссель scroll events.