Docs For AI
Performance

Rendering Performance

DOM optimization, layout thrashing, animation performance, and React/Vue optimization

Rendering Performance

Rendering performance affects how smooth and responsive your application feels. Understanding the browser rendering pipeline is key to optimization.

Browser Rendering Pipeline

Rendering Pipeline
├── JavaScript (scripting)
├── Style (CSS calculation)
├── Layout (geometry calculation)
├── Paint (fill pixels)
└── Composite (layer composition)

Optimization Goals:
- Minimize JavaScript execution time
- Reduce style recalculations
- Avoid forced synchronous layouts
- Promote animations to compositor
- Keep frame time under 16ms (60fps)

Layout Thrashing

Layout thrashing occurs when JavaScript reads and writes layout properties repeatedly.

Problem

// Bad - causes layout thrashing
function resizeAllDivs() {
  const divs = document.querySelectorAll('.box');
  divs.forEach(div => {
    // Read - forces layout
    const width = div.offsetWidth;
    // Write - invalidates layout
    div.style.width = (width * 2) + 'px';
    // Next read forces another layout...
  });
}

Solution

// Good - batch reads then writes
function resizeAllDivs() {
  const divs = document.querySelectorAll('.box');

  // Batch reads
  const widths = Array.from(divs).map(div => div.offsetWidth);

  // Batch writes
  divs.forEach((div, i) => {
    div.style.width = (widths[i] * 2) + 'px';
  });
}

// Better - use requestAnimationFrame
function resizeAllDivs() {
  const divs = document.querySelectorAll('.box');
  const widths = Array.from(divs).map(div => div.offsetWidth);

  requestAnimationFrame(() => {
    divs.forEach((div, i) => {
      div.style.width = (widths[i] * 2) + 'px';
    });
  });
}

Properties That Trigger Layout

// These properties trigger layout when read:
element.offsetTop, offsetLeft, offsetWidth, offsetHeight
element.offsetParent
element.clientTop, clientLeft, clientWidth, clientHeight
element.scrollTop, scrollLeft, scrollWidth, scrollHeight
element.getComputedStyle()
element.getBoundingClientRect()
element.scrollTo()
window.innerWidth, innerHeight
window.scrollX, scrollY

Animation Performance

CSS Animations (Preferred)

/* Good - compositor-only properties */
.animate-good {
  transform: translateX(100px);
  opacity: 0.5;
  /* These don't trigger layout or paint */
}

/* Bad - triggers layout */
.animate-bad {
  left: 100px;
  width: 200px;
  height: 200px;
}

/* Promote to compositor layer */
.promoted-layer {
  will-change: transform;
  /* or */
  transform: translateZ(0);
}

JavaScript Animations

// Good - use requestAnimationFrame
function animate(element, duration) {
  const start = performance.now();
  const initialX = 0;
  const targetX = 100;

  function frame(time) {
    const progress = Math.min((time - start) / duration, 1);
    const eased = easeOutCubic(progress);
    const x = initialX + (targetX - initialX) * eased;

    element.style.transform = `translateX(${x}px)`;

    if (progress < 1) {
      requestAnimationFrame(frame);
    }
  }

  requestAnimationFrame(frame);
}

function easeOutCubic(t) {
  return 1 - Math.pow(1 - t, 3);
}

Web Animations API

element.animate(
  [
    { transform: 'translateX(0)', opacity: 1 },
    { transform: 'translateX(100px)', opacity: 0 }
  ],
  {
    duration: 300,
    easing: 'ease-out',
    fill: 'forwards'
  }
);

Virtual Lists

For long lists, only render visible items.

// Using @tanstack/react-virtual
import { useVirtualizer } from '@tanstack/react-virtual';

function VirtualList({ items }) {
  const parentRef = useRef<HTMLDivElement>(null);

  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 50,
    overscan: 5,
  });

  return (
    <div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
      <div style={{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }}>
        {virtualizer.getVirtualItems().map((virtualItem) => (
          <div
            key={virtualItem.key}
            style={{
              position: 'absolute',
              top: 0,
              left: 0,
              width: '100%',
              height: `${virtualItem.size}px`,
              transform: `translateY(${virtualItem.start}px)`,
            }}
          >
            {items[virtualItem.index].name}
          </div>
        ))}
      </div>
    </div>
  );
}

React Optimization

Preventing Unnecessary Renders

// memo for expensive components
const ExpensiveList = memo(function ExpensiveList({ items }) {
  return items.map(item => <ExpensiveItem key={item.id} item={item} />);
});

// Custom comparison
const UserCard = memo(
  function UserCard({ user }) {
    return <div>{user.name}</div>;
  },
  (prevProps, nextProps) => prevProps.user.id === nextProps.user.id
);

useMemo and useCallback

function SearchResults({ query, items }) {
  // Memoize expensive computation
  const filteredItems = useMemo(() => {
    return items.filter(item =>
      item.name.toLowerCase().includes(query.toLowerCase())
    );
  }, [items, query]);

  // Memoize callback to prevent child re-renders
  const handleSelect = useCallback((id: number) => {
    console.log('Selected:', id);
  }, []);

  return (
    <ul>
      {filteredItems.map(item => (
        <ListItem key={item.id} item={item} onSelect={handleSelect} />
      ))}
    </ul>
  );
}

State Colocation

// Bad - state too high, causes unnecessary renders
function App() {
  const [searchQuery, setSearchQuery] = useState('');

  return (
    <div>
      <Header />  {/* Re-renders on every keystroke */}
      <SearchBar query={searchQuery} onChange={setSearchQuery} />
      <Results query={searchQuery} />
      <Footer />  {/* Re-renders on every keystroke */}
    </div>
  );
}

// Good - state colocated with consumers
function App() {
  return (
    <div>
      <Header />
      <SearchSection />  {/* Contains its own state */}
      <Footer />
    </div>
  );
}

function SearchSection() {
  const [searchQuery, setSearchQuery] = useState('');

  return (
    <>
      <SearchBar query={searchQuery} onChange={setSearchQuery} />
      <Results query={searchQuery} />
    </>
  );
}

Vue Optimization

v-once and v-memo

<template>
  <!-- Never re-renders -->
  <div v-once>{{ staticContent }}</div>

  <!-- Only re-renders when dependencies change -->
  <div v-memo="[item.id, item.selected]">
    {{ item.name }}
  </div>

  <!-- Efficient list rendering -->
  <div v-for="item in list" :key="item.id" v-memo="[item.id, item.selected]">
    {{ item.name }}
  </div>
</template>

Computed Property Caching

<script setup>
// Cached - recalculates only when dependencies change
const filteredList = computed(() => {
  return items.value.filter(item => item.active);
});

// Not cached - recalculates on every render
const getFilteredList = () => {
  return items.value.filter(item => item.active);
};
</script>

shallowRef for Large Objects

<script setup>
import { shallowRef, triggerRef } from 'vue';

// For large objects that don't need deep reactivity
const largeObject = shallowRef({ nested: { data: [] } });

function updateData() {
  largeObject.value.nested.data.push(newItem);
  triggerRef(largeObject);  // Manually trigger update
}
</script>

Debouncing and Throttling

// Debounce - wait until user stops typing
function useDebounce<T>(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => setDebouncedValue(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);

  return debouncedValue;
}

// Throttle - limit execution frequency
function useThrottle<T>(value: T, interval: number): T {
  const [throttledValue, setThrottledValue] = useState(value);
  const lastExecuted = useRef(Date.now());

  useEffect(() => {
    const now = Date.now();
    const elapsed = now - lastExecuted.current;

    if (elapsed >= interval) {
      setThrottledValue(value);
      lastExecuted.current = now;
    } else {
      const timer = setTimeout(() => {
        setThrottledValue(value);
        lastExecuted.current = Date.now();
      }, interval - elapsed);

      return () => clearTimeout(timer);
    }
  }, [value, interval]);

  return throttledValue;
}

Best Practices

Rendering Performance Guidelines

  1. Avoid layout thrashing - batch DOM reads and writes
  2. Use transform and opacity for animations
  3. Implement virtual scrolling for long lists
  4. Memoize expensive computations and callbacks
  5. Keep component state as low as possible
  6. Use debouncing/throttling for frequent updates
  7. Profile with React DevTools / Vue DevTools
  8. Target 60fps (16ms per frame)

On this page