Frontend Development 16 min read

Performance Optimization of a Complex Real‑Time Table in a Financial Front‑End Application

This article details how a newly‑graduated front‑end developer tackled severe UI lag and ten‑second first‑screen loads caused by three synchronized tables with 400+ rows of real‑time data, using Vue 3 techniques such as consolidated scroll handling, viewport‑based lazy updates, throttling, and WebSocket‑driven data subscription.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Performance Optimization of a Complex Real‑Time Table in a Financial Front‑End Application

The author describes a recent project iteration where a financial dashboard page suffered from 100% CPU usage and a ten‑second first‑screen load due to a massive, real‑time updating table that was actually composed of three synchronized tables.

Root causes :

Four‑hundred+ rows, each with twenty‑one columns, required continuous real‑time updates.

Each of the three tables listened to its own scroll event, causing three identical scroll handlers to run for a single user action.

Optimization of scroll handling – the original code attached three separate listeners:

const leftO = document.querySelector("#left");
const middleO = document.querySelector("#middle");
const rightO = document.querySelector("#right");

leftO.addEventListener("scroll", (e) => { /* sync three tables */ }, true);
middleO.addEventListener("scroll", (e) => { /* sync three tables */ }, true);
rightO.addEventListener("scroll", (e) => { /* sync three tables */ }, true);

It was refactored to record the scroll position in reactive variables and use a single watch to update the other tables, reducing the effective work from three times to one:

const leftO = document.querySelector("#left");
const middleO = document.querySelector("#middle");
const rightO = document.querySelector("#right");

const top = ref(0);
const left = ref(0);
const flag = ref("");

leftO.addEventListener("scroll", (e) => { top.value = e.target.scrollTop; left.value = e.target.scrollLeft; flag.value = 'left'; }, true);
middleO.addEventListener("scroll", (e) => { top.value = e.target.scrollTop; flag.value = 'middle'; }, true);
rightO.addEventListener("scroll", (e) => { top.value = e.target.scrollTop; left.value = e.target.scrollLeft; flag.value = 'right'; }, true);

watch(() => top.value, (newV) => {
  flag.value !== "left" && (leftO.scrollTop = newV);
  flag.value !== "middle" && (middleO.scrollTop = newV);
  flag.value !== "right" && (rightO.scrollTop = newV);
});
watch(() => left.value, (newV) => {
  flag.value !== "left" && (leftO.scrollLeft = newV);
  flag.value !== "right" && (rightO.scrollLeft = newV);
});

Optimizing real‑time data updates – instead of pushing updates for all 400+ rows, the author introduced a viewport‑based subscription model similar to image lazy‑loading. Only the IDs of rows currently visible are kept in an array and sent to the server via WebSocket; when the user scrolls, the subscription is refreshed.

function updateSubscribe(idArr) {
  // cancel previous subscription
  unsubscribe();
  // subscribe to the currently visible rows
  subscribe(idArr);
}

The demo code for detecting visible rows uses getBoundingClientRect :

const findIDArr = () => {
  const domList = document.querySelectorAll('.item');
  const visibleDom = Array.prototype.filter.call(domList, dom => isVisible(dom));
  return Array.prototype.map.call(visibleDom, dom => dom.id);
};

const isVisible = element => {
  const bounding = element.getBoundingClientRect();
  return bounding.top >= 0 && bounding.bottom <= (window.innerHeight || document.documentElement.clientHeight);
};

Throttling updates – because Vue’s reactivity would re‑render on every data change, a simple throttling mechanism was added to batch updates to once per second:

let timer;
const updateData = (resData) => {
  resData.forEach(item => {
    const Id = item.id;
    const idx = tableRow.findIndex(row => row.id == Id);
    if (idx !== -1) tableRow[idx].data = item.data;
  });
  if (!timer) {
    timer = setTimeout(() => {
      tableData.value = [...tableRow];
      timer = null;
    }, 1000);
  }
};

The author also explains the difference between toRaw (which removes reactivity without deep‑copying) and normal deep copies, and shows how to safely update individual properties versus replacing the whole reactive object.

In conclusion, the combined optimizations—consolidated scroll handling, viewport‑driven data subscription, and throttled updates—dramatically reduced CPU spikes and made the UI feel "silky" compared to the original implementation.

performanceWebSocketlazy-loadingVue3scroll
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.