# grid-virtual-scrolling

> Data grid handles large datasets efficiently with row and column virtualization.

**Framework:** vue  **Component:** Grid  **Variant:** virtual-scrolling

## Get this item

**If you are an agent, fetch the JSON.** Source is inlined, so one request is enough and no tooling is required:

```
GET https://ai.syncfusion.com/r/vue/grid-virtual-scrolling.json
```

**Install Package(s)**

```bash
npm install @syncfusion/ej2-data @syncfusion/ej2-vue-buttons @syncfusion/ej2-vue-grids @syncfusion/ej2-vue-inputs
```

**Notes**

- Syncfusion release: 2026 Volume 2 (v34.1.29)
- The Syncfusion package is licensed. The composition in this file is source you own and edit. See https://ai.syncfusion.com/licensing.md

## Source files

### src/components/grid-virtual-scrolling/Grid.vue

```vue
<template>
<div class="col-lg-12 control-section">
    <div>
        <div class='div-button'>
            <ejs-button ref="loadbtn" @click='onClick' cssClass='e-info'>Load 100K Data</ejs-button>
            <span id="popup" :style="display">
                <span id="gif" class="imagepop"></span>
            </span>
            <span id="performanceTime">{{timeTaken}}</span>
        </div>
        <ejs-grid id="VirtualScroll" ref='grid' :dataSource="virtualData" :enableVirtualization='true' :enableColumnVirtualization='true' height=400 :rowHeight='50' :dataBound='hide' :load='load' :editSettings='editSettings' :toolbar='toolbar' :clipMode="'EllipsisWithTooltip'">
            <e-columns>
              <e-column field='OrderID' headerText='Order ID' width='110' :isPrimaryKey='true' :validationRules='validationRule'></e-column>
              <e-column field='OrderDate' headerText='Order Date' width='140' format='yMd' textAlign='Right' editType='datepickeredit'></e-column>
              <e-column field='ShipDate' headerText='Ship Date' width='140' format='yMd' textAlign='Right' editType='datepickeredit'></e-column>
              <e-column field='OrderStatus' headerText='Order Status' width='140' textAlign='Center' editType='dropdownedit' :template="'orderStatusTemplate'" :validationRules='validationRule'></e-column>
              <e-column field='Priority' headerText='Priority' width='120' textAlign='Center' editType='dropdownedit' :template="'priorityTemplate'"></e-column>
              <e-column field='CustomerName' headerText='Customer Name' width='190' :validationRules='validationRule'></e-column>
              <e-column field='CustomerID' headerText='Customer ID' width='110' visible='false'></e-column>
              <e-column field='Email' headerText='Email' width='200'></e-column>
              <e-column field='Phone' headerText='Phone Number' width='140' textAlign='Right'></e-column>
              <e-column field='ShipAddress' headerText='Ship Address' width='180'></e-column>
              <e-column field='ShipCity' headerText='Ship City' width='120'></e-column>
              <e-column field='ShipState' headerText='Ship State Code' width='130'></e-column>
              <e-column field='ShipPostalCode' headerText='Ship Postal Code' width='130' textAlign='Right'></e-column>
              <e-column field='ShipCountry' headerText='Ship Country' width='150'></e-column>
              <e-column field='ProductName' headerText='Product Name' width='250'></e-column>
              <e-column field='ProductID' headerText='Product ID' width='110' visible='false'></e-column>
              <e-column field='Category' headerText='Category' width='120'></e-column>
              <e-column field='Warehouse' headerText='Ware house' width='110' visible='false' editType='dropdownedit'></e-column>
              <e-column field='InventoryCount' headerText='Inventory Count' width='150' textAlign='Right' visible='false'></e-column>
              <e-column field='Quantity' headerText='Quantity' width='100' textAlign='Right' editType='numericedit' :editParams='{ showSpinButton: false }'></e-column>
              <e-column field='UnitPrice' headerText='Unit Price' width='110' format='C2' textAlign='Right' editType='numericedit' :editParams='{ showSpinButton: false }'></e-column>
              <e-column field='Discount' headerText='Discount (%)' width='120' textAlign='Right' editType='numericedit' :editParams='{ showSpinButton: false }'></e-column>
              <e-column field='Tax' headerText='Tax (%)' width='100' textAlign='Right' editType='numericedit' :editParams='{ showSpinButton: false }'></e-column>
              <e-column field='SubTotal' headerText='Sub Total' width='110' format='C2' textAlign='Right' editType='numericedit' :editParams='{ showSpinButton: false }'></e-column>
              <e-column field='TaxAmount' headerText='Tax Amount' width='110' format='C2' textAlign='Right' editType='numericedit' :editParams='{ showSpinButton: false }'></e-column>
              <e-column field='ShipFee' headerText='Ship Fee' width='120' format='C2' textAlign='Right' editType='numericedit' :editParams='{ showSpinButton: false }'></e-column>
              <e-column field='TotalAmount' headerText='Total Amount' width='120' format='C2' textAlign='Right' editType='numericedit' :editParams='{ showSpinButton: false }'></e-column>
              <e-column field='PaymentMethod' headerText='Payment Method' width='140' editType='dropdownedit' :validationRules='validationRule' :template="'paymentMethodTemplate'"></e-column>
              <e-column field='PaymentStatus' headerText='Payment Status' width='140' textAlign='Center' editType='dropdownedit' :template="'paymentStatusTemplate'" :validationRules='validationRule'></e-column>
              <e-column field='Rating' headerText='Delivery Rating' width='160' textAlign='Center' editType='dropdownedit' :template="'ratingTemplate'" :visible='false'></e-column>
            </e-columns>
            <template v-slot:ratingTemplate="{data}">
              <div>
                <ejs-rating :value="data.Rating" cssClass="custom-rating" readOnly=true></ejs-rating>
              </div>
            </template>
            <template v-slot:paymentMethodTemplate="{data}">
              <div class="e-payment-info">
                <img :src="`src/grid/images/payment/${data.PaymentMethod}.svg`" :alt="data.PaymentMethod"/>
                <span>{{ data.PaymentMethod }}</span>
              </div>
            </template>
            <template v-slot:orderStatusTemplate="{data}">
              <div>
                <div v-if="data.OrderStatus === 'Delivered'" class="virtual-statustemp e-deliveredcolor">
                  <span class="virtual-statustxt e-deliveredcolor">Delivered</span>
                </div>
                <div v-else-if="data.OrderStatus === 'Shipped'" class="virtual-statustemp e-shippedcolor">
                  <span class="virtual-statustxt e-shippedcolor">Shipped</span>
                </div>
                <div v-else-if="data.OrderStatus === 'Packed'" class="virtual-statustemp e-packedcolor">
                  <span class="virtual-statustxt e-packedcolor">Packed</span>
                </div>
                <div v-else-if="data.OrderStatus === 'Processing'" class="virtual-statustemp e-processingcolor">
                  <span class="virtual-statustxt e-processingcolor">Processing</span>
                </div>
                <div v-else-if="data.OrderStatus === 'Canceled'" class="virtual-statustemp e-cancelcolor">
                  <span class="virtual-statustxt e-cancelcolor">Canceled</span>
                </div>
                <div v-else-if="data.OrderStatus === 'Returned'" class="virtual-statustemp e-returnedcolor">
                  <span class="virtual-statustxt e-returnedcolor">Returned</span>
                </div>
                <div v-else-if="data.OrderStatus === 'Ordered'" class="virtual-statustemp e-orderedcolor">
                  <span class="virtual-statustxt e-orderedcolor">Ordered</span>
                </div>
              </div>
            </template>
            <template v-slot:paymentStatusTemplate="{data}">
              <div>
                <div v-if="data.PaymentStatus === 'Paid'" class="virtual-statustemp e-paidcolor">
                  <span class="virtual-statustxt e-paidcolor">Paid</span>
                </div>
                <div v-else-if="data.PaymentStatus === 'Pending'" class="virtual-statustemp e-pendingcolor">
                  <span class="virtual-statustxt e-pendingcolor">Pending</span>
                </div>
                <div v-else-if="data.PaymentStatus === 'Refunded'" class="virtual-statustemp e-refundcolor">
                  <span class="virtual-statustxt e-refundcolor">Refunded</span>
                </div>
                <div v-else-if="data.PaymentStatus === 'Failed'" class="virtual-statustemp e-failedcolor">
                  <span class="virtual-statustxt e-failedcolor">Failed</span>
                </div>
              </div>
            </template>
            <template v-slot:priorityTemplate="{data}">
              <div>
                <div v-if="data.Priority === 'High'" class="virtual-statustemp e-highcolor">
                  <span class="virtual-statustxt e-highcolor">High</span>
                </div>
                <div v-else-if="data.Priority === 'Low'" class="virtual-statustemp e-lowcolor">
                  <span class="virtual-statustxt e-lowcolor">Low</span>
                </div>
                <div v-else-if="data.Priority === 'Medium'" class="virtual-statustemp e-mediumcolor">
                  <span class="virtual-statustxt e-mediumcolor">Medium</span>
                </div>
                <div v-else-if="data.Priority === 'Critical'" class="virtual-statustemp e-criticalcolor">
                  <span class="virtual-statustxt e-criticalcolor">Critical</span>
                </div>
              </div>
            </template>
        </ejs-grid>
    </div>

</div>
</template>
<script lang="ts">
import { GridComponent, ColumnsDirective, ColumnDirective, VirtualScroll, Edit, Toolbar } from "@syncfusion/ej2-vue-grids";
import { DataManager, JsonAdaptor } from '@syncfusion/ej2-data'; 
import { ButtonComponent } from "@syncfusion/ej2-vue-buttons";
import { RatingComponent } from '@syncfusion/ej2-vue-inputs';

const virtualOrderData = []; // or define proper interface if needed
function createVirtualOrderData() {
    function pmRandom(seed) {
        var t = (seed % 2147483647);
        if (t <= 0)
            t += 2147483646;
        return function () {
            t = (t * 16807) % 2147483647;
            return (t - 1) / 2147483646; // [0,1)
        };
    }
    var seed = 123456789;
    var rand = pmRandom(seed);
    function randInt(min, max) {
        return Math.floor(rand() * (max - min + 1)) + min;
    }
    function randChoice(arr) {
        return arr[Math.floor(rand() * arr.length)];
    }
    function pickAvoidTriplet(arr, fieldName) {
        var v = randChoice(arr);
        if (virtualOrderData.length >= 2) {
            var prevItems = virtualOrderData;
            var a = prevItems[virtualOrderData.length - 1][fieldName];
            var b = prevItems[virtualOrderData.length - 2][fieldName];
            if (a === b && a === v) {
                var alt = arr.filter(function (s) { return s !== v; });
                if (alt.length > 0) {
                    v = randChoice(alt);
                }
            }
        }
        return v;
    }
    var names = [
        'Maria', 'Ana Trujillo', 'Antonio Moreno', 'Thomas Hardy', 'Christina Berglund', 'Hanna Moos'
    ];
    var products = [
        'Chai', 'Chang', 'Aniseed Syrup', "Chef Anton's Cajun Seasoning",
        "Grandma's Boysenberry Spread", "Uncle Bob's Organic Dried Pears",
        'Mishi Kobe Niku', 'Ikura', 'Queso Cabrales', 'Pavlova'
    ];
    var categories = [
        'Beverages', 'Condiments', 'Confections', 'Dairy Products',
        'Grains/Cereals', 'Meat/Poultry', 'Seafood'
    ];
    var paymentMethods = ['Card', 'Digital', 'Cash'];
    var orderStatuses = ['Ordered', 'Processing', 'Packed', 'Shipped', 'Delivered', 'Canceled', 'Returned'];
    var priorities = ['Low', 'Medium', 'High', 'Critical'];
    var cities = ['Seattle', 'Austin', 'Boston', 'Chicago', 'San Francisco', 'New York'];
    var states = ['WA', 'TX', 'MA', 'IL', 'CA', 'NY'];
    var countries = ['USA', 'Canada', 'Mexico', 'UK', 'Germany', 'France'];
    var warehouses = ['WH-A', 'WH-B', 'WH-C', 'WH-D'];
    var productToCategory = {};
    for (var k = 0; k < products.length; k++) {
        productToCategory[products[k]] = categories[k % categories.length];
    }
    function computeAmounts(qty, unitPrice, discountPct, taxPct, shippingFee) {
        var gross = qty * unitPrice;
        var discount = gross * (discountPct / 100);
        var subtotal = gross - discount;
        var tax = subtotal * (taxPct / 100);
        var total = subtotal + tax + shippingFee;
        return {
            subtotal: Math.round(subtotal * 100) / 100,
            taxAmount: Math.round(tax * 100) / 100,
            totalAmount: Math.round(total * 100) / 100
        };
    }
    var baseTime = new Date(2025, 0, 1).getTime();
    for (var i = 1; i <= 100000; i++) {
        var qty = randInt(1, 10);
        var unitPrice = Math.round((rand() * 500 + 5) * 100) / 100;
        var discountPct = randInt(0, 20);
        var taxPct = randInt(0, 18);
        var shippingFee = Math.round((rand() * 20) * 100) / 100;
        var amounts = computeAmounts(qty, unitPrice, discountPct, taxPct, shippingFee);
        var orderDate = new Date(baseTime - randInt(0, 90) * 24 * 3600 * 1000);
        var shippedDate = new Date(orderDate.getTime() + randInt(1, 14) * 24 * 3600 * 1000);
        var customerId = randInt(1000, 9999);
        var custName = randChoice(names);
        var email = custName.toLowerCase().replace(/\s+/g, '.') + '@example.com';
        var paymentMethod = pickAvoidTriplet(paymentMethods, 'PaymentMethod');
        var rating = randInt(1, 5);
        var orderStatusVal = pickAvoidTriplet(orderStatuses, 'OrderStatus');
        var priorityVal = pickAvoidTriplet(priorities, 'Priority');
        var productName = randChoice(products);
        var derivedCategory = productToCategory[productName] || randChoice(categories);
        var warehouse = randChoice(warehouses);
        var inventoryCount = randInt(0, 500);
        var paymentStatus = 'Pending';
        if (orderStatusVal === 'Delivered' || orderStatusVal === 'Shipped') {
            paymentStatus = 'Paid';
        }
        else if (orderStatusVal === 'Canceled' || orderStatusVal === 'Returned') {
            if (rating <= 1) {
                paymentStatus = 'Failed';
            }
            else {
                paymentStatus = (paymentMethod === 'Cash') ? 'Pending' : 'Refunded'; // assuming 'Cash' ≈ COD
            }
        }
        else if (orderStatusVal === 'Packed') {
            paymentStatus = (paymentMethod === 'Cash') ? 'Pending' : 'Paid';
        }
        else if (orderStatusVal === 'Ordered' || orderStatusVal === 'Processing') {
            paymentStatus = 'Pending';
        }
        virtualOrderData.push({
            OrderID: "ORD-".concat(1000 + i),
            OrderDate: orderDate,
            ShipDate: shippedDate,
            CustomerID: "CUS-".concat(customerId),
            CustomerName: custName,
            Email: email,
            Phone: "+1-".concat(randInt(200, 999), "-").concat(randInt(1000, 9999)),
            ShipAddress: "".concat(randInt(10, 999), " ").concat(randChoice(['Main St', 'Market St', '1st Ave', 'Broadway'])),
            ShipCity: randChoice(cities),
            ShipState: randChoice(states),
            ShipPostalCode: String(randInt(10000, 99999)),
            ShipCountry: randChoice(countries),
            ProductID: "PROD-".concat(randInt(10000, 99999)),
            ProductName: productName,
            Category: derivedCategory,
            Quantity: qty,
            UnitPrice: unitPrice,
            Discount: discountPct,
            Tax: taxPct,
            SubTotal: amounts.subtotal,
            TaxAmount: amounts.taxAmount,
            ShipFee: shippingFee,
            TotalAmount: amounts.totalAmount,
            PaymentMethod: paymentMethod,
            PaymentStatus: paymentStatus,
            Warehouse: warehouse,
            InventoryCount: inventoryCount,
            Priority: priorityVal,
            OrderStatus: orderStatusVal,
            Rating: rating,
        });
    }
}

export default {
  components: {
    'ejs-grid': GridComponent,
    'e-columns': ColumnsDirective,
    'e-column': ColumnDirective,
    'ejs-button': ButtonComponent,
    'ejs-rating': RatingComponent
  },
  data: function() {
    return {
        flag: true, date1: 0, date2: 0, virtualData: [], timeTaken: 'Time Taken: 0 ms',
        display: {'display': 'none'},
        editSettings: { allowEditing: true, allowDeleting: true, newRowPosition: 'Top' },
        toolbar: ['Add', 'Edit', 'Delete', 'Update', 'Cancel'],
        validationRule: { required: true },
    };
  },
  methods: {
    show: function() {
        (this as any).display = {'display': 'inline-block'};
    },
    hide: function() {
      if ((this as any).flag && (this as any).date1) {
        (this as any).date2 = new Date().getTime();
        (this as any).timeTaken = "Time Taken: " + ((this as any).date2 - (this as any).date1) + "ms";
        ((this as any).$refs.grid).ej2Instances.editSettings.allowAdding= true;
        (this as any).flag = false;
      }
      (this as any).display = {'display': 'none'};
    },
    onClick: function (args: any) {
      const loadButton = (((this as any).$refs.loadbtn) as any)?.ej2Instances;           
      loadButton.disabled = true;    
      if (!virtualOrderData.length) {
        (this as any).show();      
        createVirtualOrderData();
        (this as any).virtualData = virtualOrderData;
        (this as any).date1 = new Date().getTime();
        (this as any).flag = true;
      }
    },
    load: function (args: LoadEventArgs) {
      if (((this as any).$refs.grid).ej2Instances.enableVirtualization) {
        args.enableSeamlessScrolling = true;
      }
    }
  },
  provide: {
      grid: [VirtualScroll, Toolbar, Edit]
  }
};
</script>
```

### src/components/grid-virtual-scrolling/GridView.vue

```vue
<template>
  <Grid />
</template>

<script setup>
import Grid from '../../components/Grid.vue'
</script>
```
