SoFunction
Updated on 2025-04-07

Implementation of table scrolling in antd4

First of all, the underlying implementation of antd4 table is rc-table, let’s take a look at it from rc-table.

1. The method of maintaining the same frequency scrolling in the header, Footer, and TableBody in the rc-table

Scenario: The content area of ​​the Table is larger than the width of the container Table, and the Table is set with scrollX, both Header and Footer, so you can pay attention to the same frequency scrolling

So how is it done?

  • Listen to the onScroll method to get the distance scrollLeft of the scrollbar scroll to the left;
  • Set scrollLeft for three doms at the same time

2. OnScroll implementation in rc-table

First look at the general onScroll implementation

  • Listen to onScroll to get scrollLeft
  • Set scrollLeft for header, footer, tableBody

Here is the pseudo-code

const onScroll = (e: ScrollEvent) => {
    // Get scrollLeft    const scrollLeft = 
    // Set scrollLeft for all headers, footer, and table-body     = scrollLeft
     = scrollLeft
     = scrollLeft
}

Implementation of onScroll in source code

 const onScroll = ({
    currentTarget,
    scrollLeft,
  }: {
    currentTarget: HTMLElement;
    scrollLeft?: number;
  }) => {
    const mergedScrollLeft = typeof scrollLeft === 'number' ? scrollLeft : ;

    const compareTarget = currentTarget || EMPTY_SCROLL_TARGET; 
    if (!getScrollTarget() || getScrollTarget() === compareTarget) { 
      setScrollTarget(compareTarget);
      //A scrolling requires controlling all synchronous scrolling of header, body, summary, stickyScrollBar            // header set scrollLeft             = mergedScrollLeft
            // body set scrollLeft             = mergedScrollLeft
    }
  };

Comparing the two implementations, we can see that the implementation in rc-table has an additional parameter scrollLeft and an if judgment;
Why is there one more entry parameter and one judgment? Keep reading?

3. Scrolling monitoring of Header and Footer

  • Implement it with the component FixedHolder and bind ref to FixedHolder;
  • The one listening is onWheel, not onScroll;

Why is listening onWheel not onScroll?

(() => {
      function onWheel(e: WheelEvent) {
        // deltaX: Returns a double representing the horizontal scroll amount
        const { currentTarget, deltaX } = e as unknown as <HTMLDivElement>;
        // Avoid triggering unnecessary scrolling, is an optimization        if (deltaX) {
          onScroll({ currentTarget, scrollLeft:  + deltaX });
          ();
        }
      }
      ?.addEventListener('wheel', onWheel);

      return () => {
        ?.removeEventListener('wheel', onWheel);
      };
    }, []);

Don't confuse oncroll with onwheel. onwheel is the mouse wheel rotation, while onscroll handles scrolling events in the content area inside the object.
When the dom satisfies any of the following items, onScroll will not be triggered;

  • overflow:hidden
  • The scroll bar does not exist

FixHolder Components

Set style overflow:hidden;

<div
        style={{
          overflow: 'hidden',
          ...(isSticky ? { top: stickyTopOffset, bottom: stickyBottomOffset } : {}),
        }}
        ref={setScrollRef}
        className={classNames(className, {
          [stickyClassName]: !!stickyClassName,
        })}
                />
                <table
          style={{
            tableLayout: 'fixed',
            visibility: noData || mergedColumnWidth ? null : 'hidden',
          }}
        >
          {(!noData || !maxContentScroll || allFlattenColumnsWithWidth) && (
            <ColGroup
              colWidths={mergedColumnWidth ? [...mergedColumnWidth, combinationScrollBarSize] : []}
              columCount={columCount + 1}
              columns={flattenColumnsWithScrollbar}
            />
          )}
          {children({
            ...props,
            stickyOffsets: headerStickyOffsets,
            columns: columnsWithScrollbar,
            flattenColumns: flattenColumnsWithScrollbar,
          })}
        </table>
                </div>

Ref, call useCallback to assign dom; use listening to wheel event, convert it to onScroll, and add the parameter scrollLeft;

const setScrollRef = ((element: HTMLElement) => {
       = element;
    }, []);

4. TableBody scrolling

Of course it is listening to onScroll events;
When setting scrollX for Tables, TableBody sets the style {overflow-x: auto} so that there will be scrolling in the same frequency.

<div
  style={
    ...scrollXStyle,
    ...scrollYStyle
  }
          onScroll={onScroll}
          ref={scrollBodyRef}
        >
          <TableComponent>
            {bodyColGroup}
            {bodyTable}
          </TableComponent>
        </div>

5. Very nice point

Get the currently executing dom

const [setScrollTarget, getScrollTarget] = useTimeoutLock(null);

getScrollTarget is used to obtain the currently executing dom
Use useState to store the executing dom; when the component re-renders and the dom is updated, the executing dom at this time changes when the next render is next; useRef does not re-assign the value before the next render, and still retains the same value as the last time;
UseRef + setTimeout to implement in the source code; useRef is used to store the currently executing dom; setTimeout is used to throttle;
where getState gets the current state being executed, which may be empty; setState sets the current State and clears the set state after 100ms;

export function useTimeoutLock&lt;State&gt;(defaultState?: State): [(state: State) =&gt; void, () =&gt; State | null] {
  const frameRef = useRef&lt;State | null&gt;(defaultState || null);
  const timeoutRef = useRef&lt;number&gt;();

  function cleanUp() {
    ();
  }

  function setState(newState: State) {
     = newState;
        // Clear the last timer    cleanUp();
    
     = (() =&gt; {
       = null;
       = undefined;
    }, 100);
  }

  function getState() {
    return ;
  }

  useEffect(() =&gt; cleanUp, []);

  return [setState, getState];
}

Why is it set if judgment onScroll

Before gettingScrollTarget() calls onScroll, is there a scrolling dom; if there is no update; if there is, determine whether it is the same as triggering onScroll; if it is updated; the purpose is to avoid the execution of the next onScroll, and the execution of the next onScroll is trapped in a loop, which is equivalent to throttling; the throttling of the hooks version

const compareTarget = currentTarget || EMPTY_SCROLL_TARGET; 
// Fixed scrolling item// When processing the previous scroll, the next scroll is prohibited from scrolling and executing onScroll.if (!getScrollTarget() || getScrollTarget() === compareTarget) {
    setScrollTarget(compareTarget);
}

When looking at this logic, optimize the details 👍; from the perspective of hooks, realize throttling; wheel and scroll are both scrolling, but there are also differences; and in react, it supports dom binding onScroll and onWheel;

Related links:

rc-table: /react-component/table
antd-table: /components/table-cn#components-table-demo-fixed-columns

This is the end of this article about the implementation of table scrolling in antd4. For more related content on antd4 table scrolling, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!