You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Salesforce中实现HTML Table无限滚动,解决大行数卡顿问题

Great question! Dealing with large HTML tables in Salesforce can get really sluggish once you pass the 100-row mark—implementing infinite scroll is the perfect fix. Let’s break down the best approaches based on which Salesforce framework you’re working with:

This is the easiest and most Salesforce-native approach since lightning-datatable has built-in support for infinite scrolling. No need to reinvent the wheel here!

Step 1: Datatable Markup

Add the enable-infinite-loading attribute and hook up the onloadmore event:

<template>
    <lightning-datatable
        key-field="Id"
        data={data}
        columns={columns}
        enable-infinite-loading={isInfiniteLoadingEnabled}
        onloadmore={handleLoadMore}
        hide-checkbox-column
    ></lightning-datatable>
</template>

Step 2: JavaScript Controller

Fetch records in batches (50-100 is ideal) and append them to your existing dataset. Don’t forget to stop loading once there are no more records:

import { LightningElement, track, wire } from 'lwc';
import getAccounts from '@salesforce/apex/AccountController.getAccounts';

export default class InfiniteScrollDatatable extends LightningElement {
    @track data = [];
    @track columns = [
        { label: 'Name', fieldName: 'Name', type: 'text' },
        { label: 'Industry', fieldName: 'Industry', type: 'text' },
        { label: 'Revenue', fieldName: 'AnnualRevenue', type: 'currency' }
    ];
    @track isInfiniteLoadingEnabled = true;
    offset = 0;
    batchSize = 50;

    connectedCallback() {
        this.loadInitialData();
    }

    async loadInitialData() {
        const result = await getAccounts({ offset: this.offset, limit: this.batchSize });
        this.data = result;
        this.offset += this.batchSize;
        // Disable infinite loading if we got fewer records than the batch size
        if (result.length < this.batchSize) {
            this.isInfiniteLoadingEnabled = false;
        }
    }

    async handleLoadMore(event) {
        // Show loading spinner
        event.target.isLoading = true;
        
        const result = await getAccounts({ offset: this.offset, limit: this.batchSize });
        // Append new records to existing data
        this.data = [...this.data, ...result];
        this.offset += this.batchSize;

        // Stop loading and disable if no more records
        event.target.isLoading = false;
        if (result.length < this.batchSize) {
            this.isInfiniteLoadingEnabled = false;
        }
    }
}

Step 3: Apex Controller

Create a server-side method to fetch batched records with OFFSET and LIMIT:

public with sharing class AccountController {
    @AuraEnabled(cacheable=true)
    public static List<Account> getAccounts(Integer offset, Integer limit) {
        return [
            SELECT Id, Name, Industry, AnnualRevenue
            FROM Account
            ORDER BY Name
            LIMIT :limit OFFSET :offset
        ];
    }
}

2. Aura Components - Custom Infinite Scroll

If you’re still working with Aura, you’ll need to implement scroll event listeners manually. Here’s a straightforward setup:

Component Markup

Add a scrollable container and table, plus a loading spinner:

<aura:component controller="AccountController">
    <aura:attribute name="accounts" type="Account[]" />
    <aura:attribute name="offset" type="Integer" default="0" />
    <aura:attribute name="batchSize" type="Integer" default="50" />
    <aura:attribute name="hasMoreRecords" type="Boolean" default="true" />

    <div class="scroll-container" onscroll="{!c.handleScroll}" style="height:500px; overflow-y:auto;">
        <table class="slds-table slds-table_bordered slds-table_cell-buffer">
            <thead>
                <tr>
                    <th scope="col"><div class="slds-truncate" title="Name">Name</div></th>
                    <th scope="col"><div class="slds-truncate" title="Industry">Industry</div></th>
                    <th scope="col"><div class="slds-truncate" title="Revenue">Annual Revenue</div></th>
                </tr>
            </thead>
            <tbody>
                <aura:iteration items="{!v.accounts}" var="acc">
                    <tr>
                        <td><div class="slds-truncate">{!acc.Name}</div></td>
                        <td><div class="slds-truncate">{!acc.Industry}</div></td>
                        <td><div class="slds-truncate">{!acc.AnnualRevenue}</div></td>
                    </tr>
                </aura:iteration>
                <aura:if isTrue="{!v.hasMoreRecords}">
                    <tr>
                        <td colspan="3" class="slds-text-align_center">
                            <lightning:spinner alternativeText="Loading more records..." />
                        </td>
                    </tr>
                </aura:if>
            </tbody>
        </table>
    </div>
</aura:component>

Controller & Helper JS

Listen for scroll events, load new batches, and append them to the table:

// Controller.js
({
    init: function(component, event, helper) {
        helper.loadRecords(component);
    },

    handleScroll: function(component, event, helper) {
        const container = event.target;
        // Trigger load when user is 100px from the bottom
        if (container.scrollTop + container.clientHeight >= container.scrollHeight - 100 && component.get('v.hasMoreRecords')) {
            helper.loadRecords(component);
        }
    }
})

// Helper.js
({
    loadRecords: function(component) {
        const action = component.get('c.getAccounts');
        const offset = component.get('v.offset');
        const batchSize = component.get('v.batchSize');

        action.setParams({ offset: offset, limit: batchSize });
        action.setCallback(this, function(response) {
            const state = response.getState();
            if (state === 'SUCCESS') {
                const newRecords = response.getReturnValue();
                component.set('v.accounts', component.get('v.accounts').concat(newRecords));
                component.set('v.offset', offset + batchSize);
                
                // Stop loading if we hit the end of the dataset
                if (newRecords.length < batchSize) {
                    component.set('v.hasMoreRecords', false);
                }
            }
        });
        $A.enqueueAction(action);
    }
})

3. Visualforce Pages - Custom Infinite Scroll

For Visualforce, use JavaScript to listen for scroll events and call Apex via remoting:

Visualforce Page

<apex:page controller="AccountController" showHeader="false" standardStylesheets="true">
    <style>
        .scroll-container { height: 500px; overflow-y: auto; }
    </style>

    <div class="scroll-container" id="tableContainer">
        <table class="slds-table slds-table_bordered slds-table_cell-buffer">
            <thead>
                <tr>
                    <th scope="col"><div class="slds-truncate" title="Name">Name</div></th>
                    <th scope="col"><div class="slds-truncate" title="Industry">Industry</div></th>
                    <th scope="col"><div class="slds-truncate" title="Revenue">Annual Revenue</div></th>
                </tr>
            </thead>
            <tbody id="tableBody">
                <apex:repeat value="{!accounts}" var="acc">
                    <tr>
                        <td><div class="slds-truncate">{!acc.Name}</div></td>
                        <td><div class="slds-truncate">{!acc.Industry}</div></td>
                        <td><div class="slds-truncate">{!acc.AnnualRevenue}</div></td>
                    </tr>
                </apex:repeat>
            </tbody>
        </table>
        <div id="loadingSpinner" style="display:none; text-align:center; padding:10px;">
            <img src="/img/loading32.gif" alt="Loading..." />
        </div>
    </div>

    <script>
        let offset = {!batchSize};
        const batchSize = {!batchSize};
        let hasMoreRecords = true;
        let isLoading = false;

        document.getElementById('tableContainer').addEventListener('scroll', function() {
            if (!hasMoreRecords || isLoading) return;
            
            const container = this;
            if (container.scrollTop + container.clientHeight >= container.scrollHeight - 100) {
                loadMoreRecords();
            }
        });

        function loadMoreRecords() {
            isLoading = true;
            document.getElementById('loadingSpinner').style.display = 'block';

            Visualforce.remoting.Manager.invokeAction(
                '{!$RemoteAction.AccountController.getAccounts}',
                offset,
                batchSize,
                function(result, event) {
                    if (event.status) {
                        if (result.length > 0) {
                            let rows = '';
                            result.forEach(acc => {
                                rows += `<tr>
                                    <td><div class="slds-truncate">${acc.Name}</div></td>
                                    <td><div class="slds-truncate">${acc.Industry || ''}</div></td>
                                    <td><div class="slds-truncate">${acc.AnnualRevenue || ''}</div></td>
                                </tr>`;
                            });
                            document.getElementById('tableBody').innerHTML += rows;
                            offset += batchSize;
                        } else {
                            hasMoreRecords = false;
                        }
                    }
                    isLoading = false;
                    document.getElementById('loadingSpinner').style.display = 'none';
                },
                { escape: true }
            );
        }
    </script>
</apex:page>

Apex Controller for Visualforce

public with sharing class AccountController {
    public Integer batchSize { get; set; }
    public List<Account> accounts { get; set; }

    public AccountController() {
        batchSize = 50;
        accounts = getAccounts(0, batchSize);
    }

    @RemoteAction
    public static List<Account> getAccounts(Integer offset, Integer limit) {
        return [
            SELECT Id, Name, Industry, AnnualRevenue
            FROM Account
            ORDER BY Name
            LIMIT :limit OFFSET :offset
        ];
    }
}

Key Tips for All Approaches

  • Batch Size: Stick to 50-100 records per batch—larger batches can still cause lag, smaller ones mean more server calls.
  • Ordering: Always include an ORDER BY clause in SOQL to ensure consistent record ordering across batches.
  • Loading State: Show a spinner while fetching records to keep users informed.
  • Stop Loading When Done: Disable scroll listeners or infinite loading flags once there are no more records to fetch.
  • SOQL Limits: Test with large datasets to avoid hitting Salesforce’s query row limits.

内容的提问来源于stack exchange,提问作者Rajnarayanan Ramanathan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 09:11:19