vue svelte solid 虚拟滚动性能对比

news2024/12/25 23:53:10

前言

由于svelte solid 两大无虚拟DOM框架,由于其性能好,在前端越来越有影响力。

因此本次想要验证,这三个框架关于实现表格虚拟滚动的性能。

比较版本

  • vue@3.4.21
  • svelte@4.2.12
  • solid-js@1.8.15

比较代码

这里使用了我的 stk-table-vue(npm) 中实现虚拟滚动主要代码。

StkTable.vue

<script setup>
import { onMounted, ref, computed } from 'vue';
const props = defineProps({
    virtual: {
        type: Boolean,
        default: true
    },
    columns: {
        type: Array
    },
    dataSource: {
        type: Array
    }
})
const tableContainer = ref();
const virtualScroll = ref({
    containerHeight: 0,
    startIndex: 0, // 数组开始位置
    rowHeight: 28,
    offsetTop: 0, // 表格定位上边距
    scrollTop: 0, // 纵向滚动条位置,用于判断是横向滚动还是纵向
});

const dataSourceCopy = computed(() => [...props.dataSource]);

const virtual_pageSize = computed(() => Math.ceil(virtualScroll.value.containerHeight / virtualScroll.value.rowHeight) + 1); // 这里最终+1,因为headless=true无头时,需要上下各预渲染一行。

const virtual_on = computed(() => props.virtual && dataSourceCopy.value.length > virtual_pageSize.value * 2)
const virtual_dataSourcePart = computed(() => virtual_on.value
    ? dataSourceCopy.value.slice(virtualScroll.value.startIndex, virtualScroll.value.startIndex + virtual_pageSize.value)
    : dataSourceCopy.value)
const virtual_offsetBottom = computed(() => virtual_on.value ? (dataSourceCopy.value.length - virtualScroll.value.startIndex - virtual_dataSourcePart.value.length) * virtualScroll.value.rowHeight : 0)


onMounted(() => {
    initVirtualScroll();
})

/**
   * 初始化虚拟滚动参数
   * @param {number} [height] 虚拟滚动的高度
   */
function initVirtualScroll(height) {
    initVirtualScrollY(height);
    // this.initVirtualScrollX();
}
/**
 * 初始化Y虚拟滚动参数
 * @param {number} [height] 虚拟滚动的高度
 */
function initVirtualScrollY(height) {
    if (virtual_on.value) {
        virtualScroll.value.containerHeight = typeof height === 'number' ? height : tableContainer.value?.offsetHeight;
        updateVirtualScrollY(tableContainer.value?.scrollTop);
    }
}
/** 通过滚动条位置,计算虚拟滚动的参数 */
function updateVirtualScrollY(sTop = 0) {
    const { rowHeight } = virtualScroll.value;
    const startIndex = Math.floor(sTop / rowHeight);
    Object.assign(virtualScroll.value, {
        startIndex,
        offsetTop: startIndex * rowHeight, // startIndex之前的高度
    });
}

function onTableScroll(e) {
    if (!e?.target) return;

    // 此处可优化,因为访问e.target.scrollXX消耗性能
    const { scrollTop, scrollLeft } = e.target;
    // 纵向滚动有变化
    if (scrollTop !== virtualScroll.value.scrollTop) virtualScroll.value.scrollTop = scrollTop;
    if (virtual_on.value) {
        updateVirtualScrollY(scrollTop);
    }
}
</script>
<template>
    <div class="stk-table" ref="tableContainer" @scroll="onTableScroll">
        <table class="stk-table-main">
            <thead>
                <tr>
                    <th v-for="col in columns" :key="col.dataIndex" :data-col-key="col.dataIndex">{{ col.title || '--' }}
                    </th>
                </tr>
            </thead>
            <tbody>
                <tr :style="{ height: `${virtualScroll.offsetTop}px` }"></tr>
                <tr v-for="row in virtual_dataSourcePart" :key="row.id">
                    <td v-for="col in columns" :key="col.dataIndex">{{ row[col.dataIndex] || '--' }}</td>
                </tr>
                <tr :style="{ height: `${virtual_offsetBottom}px` }"></tr>
            </tbody>
        </table>
    </div>
</template>

StkTable.svelte

<script>
  import { onMount } from 'svelte';
  import '../stk-table/stk-table.less';
  
  export let style = '';
  export let virtual = true;

  let tableContainer;

  let virtualScroll = {
    containerHeight: 0,
    startIndex: 0, // 数组开始位置
    rowHeight: 28,
    offsetTop: 0, // 表格定位上边距
    scrollTop: 0, // 纵向滚动条位置,用于判断是横向滚动还是纵向
  };
  export let columns = [
    { dataIndex: 'id', title: 'ID' },
    { dataIndex: 'name', title: 'Name' },
  ];

  export let dataSource = [];

  $: dataSourceCopy = [...dataSource];
  /** 数据量大于2页才开始虚拟滚动*/

  /** 虚拟滚动展示的行数 */
  $: virtual_pageSize = Math.ceil(virtualScroll.containerHeight / virtualScroll.rowHeight) + 1; // 这里最终+1,因为headless=true无头时,需要上下各预渲染一行。
  $: virtual_on = virtual && dataSourceCopy.length > virtual_pageSize * 2;

  /** 虚拟滚动展示的行 */
  $: virtual_dataSourcePart = virtual_on
    ? dataSourceCopy.slice(virtualScroll.startIndex, virtualScroll.startIndex + virtual_pageSize)
    : dataSourceCopy;
  /** 虚拟表格定位下边距*/
  $: virtual_offsetBottom = virtual_on ?(dataSourceCopy.length - virtualScroll.startIndex - virtual_dataSourcePart.length) * virtualScroll.rowHeight : 0;

  onMount(() => {
    initVirtualScroll();
  });

  /**
   * 初始化虚拟滚动参数
   * @param {number} [height] 虚拟滚动的高度
   */
  function initVirtualScroll(height) {
    initVirtualScrollY(height);
    // this.initVirtualScrollX();
  }
  /**
   * 初始化Y虚拟滚动参数
   * @param {number} [height] 虚拟滚动的高度
   */
  function initVirtualScrollY(height) {
    if(virtual_on){
      virtualScroll.containerHeight = typeof height === 'number' ? height : tableContainer?.offsetHeight;
      virtualScroll = virtualScroll;
      updateVirtualScrollY(tableContainer?.scrollTop);
    }
  }
  /** 通过滚动条位置,计算虚拟滚动的参数 */
  function updateVirtualScrollY(sTop = 0) {
    const { rowHeight } = virtualScroll;
    const startIndex = Math.floor(sTop / rowHeight);
    Object.assign(virtualScroll, {
      startIndex,
      offsetTop: startIndex * rowHeight, // startIndex之前的高度
    });
  }

  function onTableScroll(e) {
    if (!e?.target) return;

    // 此处可优化,因为访问e.target.scrollXX消耗性能
    const { scrollTop, scrollLeft } = e.target;
    // 纵向滚动有变化
    if (scrollTop !== virtualScroll.scrollTop) virtualScroll.scrollTop = scrollTop;
    if (virtual_on) {
      updateVirtualScrollY(scrollTop);
    }
  }
</script>

<div class="stk-table" bind:this={tableContainer} {style} on:scroll={onTableScroll}>
  <table class="stk-table-main">
    <thead>
      <tr>
        {#each columns as col (col.dataIndex)}
          <th data-col-key={col.dataIndex}>{col.title || '--'}</th>
        {/each}
      </tr>
    </thead>
    <tbody>
      <tr style="height: {`${virtualScroll.offsetTop}px`}"></tr>
      {#each virtual_dataSourcePart as row (row.id)}
        <tr>
          {#each columns as col (col.dataIndex)}
            <td>{row[col.dataIndex] || '--'}</td>
          {/each}
        </tr>
      {/each}
      <tr style="height: {`${virtual_offsetBottom}px`}"></tr>
    </tbody>
  </table>
</div>

StkTable.jsx (solid-js)

import { For, createSignal, onMount } from "solid-js";
import '../stk-table/stk-table.less';

export function StkTable(props) {
    let tableContainer;
    const [virtualScroll, setVirtualScroll] = createSignal({
        containerHeight: 0,
        startIndex: 0, // 数组开始位置
        rowHeight: 28,
        offsetTop: 0, // 表格定位上边距
        scrollTop: 0, // 纵向滚动条位置,用于判断是横向滚动还是纵向
    });

    const [dataSourceCopy, setDataSourceCopy] = createSignal([]);

    const virtual_pageSize = () => {
        const vs = virtualScroll();
        return Math.ceil(vs.containerHeight / vs.rowHeight) + 1
    }; // 这里最终+1,因为headless=true无头时,需要上下各预渲染一行。
    const virtual_on = () => props.virtual && dataSourceCopy().length > virtual_pageSize() * 2;
    /** 虚拟滚动展示的行 */
    const virtual_dataSourcePart = () => {
        const vs = virtualScroll();
        const pageSize = virtual_pageSize();
        console.log(vs, pageSize)
        return virtual_on()
            ? dataSourceCopy().slice(vs.startIndex, vs.startIndex + pageSize)
            : dataSourceCopy()
    };
    /** 虚拟表格定位下边距*/
    const virtual_offsetBottom = () => virtual_on() ? (dataSourceCopy().length - virtualScroll().startIndex - virtual_dataSourcePart().length) * virtualScroll().rowHeight : 0;


    onMount(() => {
        setDataSourceCopy([...props.dataSource]);
        initVirtualScroll();
    });

    /**
   * 初始化虚拟滚动参数
   * @param {number} [height] 虚拟滚动的高度
   */
    function initVirtualScroll(height) {
        initVirtualScrollY(height);
        // this.initVirtualScrollX();
    }

    /**
       * 初始化Y虚拟滚动参数
       * @param {number} [height] 虚拟滚动的高度
       */
    function initVirtualScrollY(height) {
        if (virtual_on()) {
            const vs = virtualScroll()
            vs.containerHeight = typeof height === 'number' ? height : tableContainer?.offsetHeight;
            setVirtualScroll(vs);
            updateVirtualScrollY(tableContainer?.scrollTop);
        }
    }
    /** 通过滚动条位置,计算虚拟滚动的参数 */
    function updateVirtualScrollY(sTop = 0) {
        let vs = virtualScroll();
        const startIndex = Math.floor(sTop / vs.rowHeight);
        Object.assign(vs, {
            startIndex,
            offsetTop: startIndex * vs.rowHeight, // startIndex之前的高度
        });
        setVirtualScroll({...vs});// 必须扩展运算,否则不触发更新
    }
    function onTableScroll(e) {
        if (!e?.target) return;

        // 此处可优化,因为访问e.target.scrollXX消耗性能
        const { scrollTop, scrollLeft } = e.target;
        const vs = virtualScroll()
        // 纵向滚动有变化
        if (scrollTop !== vs.scrollTop) {
            vs.scrollTop = scrollTop;
            setVirtualScroll(vs);
        }
        if (virtual_on()) {
            updateVirtualScrollY(scrollTop);
        }
    }

    return <div class="stk-table" ref={tableContainer} style={props.style} onScroll={onTableScroll}>
        <table class="stk-table-main">
            <thead>
                <tr>
                    <For each={props.columns}>{
                        (col) =>
                            <th data-col-key={col.dataIndex}>{col.title || '--'}</th>
                    }</For>
                </tr>
            </thead>
            <tbody>
                <tr style={{ height: `${virtualScroll().offsetTop}px` }}></tr>
                <For each={virtual_dataSourcePart()}>{
                    (row) =>
                        <tr>
                            <For each={props.columns}>{
                                (col) => <td>{row[col.dataIndex] || '--'}</td>
                            }</For>
                        </tr>
                }</For>
                <tr style={{ height: `${virtual_offsetBottom()}px` }}></tr>
            </tbody>
        </table>
    </div>
}

style.less

src/StkTable/style.less · JA+/stk-table-vue

性能比较

比较虚拟滚动性能。

通过长按键盘↓键滚动,且将电脑cpu速度调至减速6x

观察浏览器开发者面板performance标签下的任务耗时。

vue任务

svelte任务

solid任务

结论

观察solid任务的超时情况(红色部分),大体比vue与svelte要少。

solid与svelte 相较vue在虚拟滚动情况下,未能观察到明显优势。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1489756.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

GIN与Echo:选择正确Go框架的指南

您是否在Go中构建Web应用&#xff1f;选择正确的框架至关重要&#xff01;GIN和Echo是两个热门选择&#xff0c;每个都有其优势和特点。本指南将详细介绍每个框架的特性、速度、社区热度以及它们各自擅长的项目类型。最后&#xff0c;您将能够为您的下一个Web项目选择完美的框架…

SpringBoot + Disruptor 实现特快高并发处理

使用Disruptor做消息队列&#xff0c;解决内存队列的延迟问题&#xff08;在性能测试中发现竟然与I/O操作处于同样的数量级&#xff09; 【基于 Disruptor 开发的系统单线程能支撑每秒 600 万订单】 核心概念&#xff1a; Ring Buffer 环形的缓冲区&#xff0c;从3.0版本开始…

SQL 查询一张卡的最新使用记录

SQL 查询一张卡的最新使用记录 1. 问题描述 1. 问题描述 一张卡&#xff0c;有一个底表记录这个卡的基本信息&#xff0c;还有一个使用卡的记录表&#xff0c;记录着&#xff0c;这张卡的使用记录&#xff0c;但我们要获取这张卡的最新使用记录&#xff0c;该如何写SQL呢&…

【Linux命令】fuser

fuser 使用文件或文件结构识别进程。 详细 fuser命令用于报告进程使用的文件和网络套接字。fuser命令列出了本地进程的进程号&#xff0c;哪些本地进程使用file&#xff0c;参数指定的本地或远程文件。 每个进程号后面都跟随一个字母&#xff0c;该字母指示进程如何使用该文…

Python实现CCI工具判断信号:股票技术分析的工具系列(5)

Python实现CCI工具判断信号&#xff1a;股票技术分析的工具系列&#xff08;5&#xff09; 介绍算法解释 代码rolling函数介绍完整代码data代码CCI.py 介绍 在股票技术分析中&#xff0c;CCI (商品路径指标&#xff09;是一种常用的技术指标&#xff0c;用于衡量股价是否处于超…

MATLAB知识点:使用for循环时需要注意的事项

​讲解视频&#xff1a;可以在bilibili搜索《MATLAB教程新手入门篇——数学建模清风主讲》。​ MATLAB教程新手入门篇&#xff08;数学建模清风主讲&#xff0c;适合零基础同学观看&#xff09;_哔哩哔哩_bilibili 节选自​第4章&#xff1a;MATLAB程序流程控制 在使用for循环…

HarmonyOS—HAP唯一性校验逻辑

HAP是应用安装的基本单位&#xff0c;在DevEco Studio工程目录中&#xff0c;一个HAP对应一个Module。应用打包时&#xff0c;每个Module生成一个.hap文件。 应用如果包含多个Module&#xff0c;在应用市场上架时&#xff0c;会将多个.hap文件打包成一个.app文件&#xff08;称…

第 125 场 LeetCode 双周赛题解

A 超过阈值的最少操作数 I 排序然后查找第一个大于等于 k 的元素所在的位置 class Solution { public:int minOperations(vector<int> &nums, int k) {sort(nums.begin(), nums.end());return lower_bound(nums.begin(), nums.end(), k) - nums.begin();} };B 超过阈…

数据结构(一)综述

一、常见的数据结构 数据结构优点缺点数组查找快增删慢链表增删快查找慢哈希表增删、查找都快数据散列&#xff0c;对存储空间有浪费栈顶部元素插入和取出快除顶部元素外&#xff0c;存取其他元素都很慢队列顶部元素取出和尾部元素插入快存取其他元素都很慢二叉树增删、查找都快…

自学高效备考2025年AMC8数学竞赛:2000-2024年AMC8真题解析

今天继续来随机看五道AMC8的真题和解析&#xff0c;根据实践经验&#xff0c;对于想了解或者加AMC8美国数学竞赛的孩子来说&#xff0c;吃透AMC8历年真题是备考最科学、最有效的方法之一。即使不参加AMC8竞赛&#xff0c;吃透了历年真题600道和背后的知识体系&#xff0c;那么小…

深入理解Tomcat

目录&#xff1a; TomcatTomcat简介如何下载tomcatTomcat工作原理Tomcat架构图Tomcat组件Server组件Service组件Connector组件Engine组件Host组件Context组件 配置虚拟主机(Host)配置Context Tomcat Tomcat简介 Tomcat服务器是Apache的一个开源免费的Web容器。它实现了JavaEE…

计算机网络-物理层-传输媒体

传输媒体的分类 导向型-同轴电缆 导向型-双绞线 导向型-光纤 非导向型

卡密交易系统 卡密社区SUP系统源码 分销系统平台 分销商城系统开发

卡密社区SUP系统总控源码主站分销系统功能源码 跟以前的卡盟那种控制端差不多总控可以给别人开通&#xff0c;分销&#xff0c;主站&#xff0c;类似自己做系统商一样&#xff0c;自助发卡&#xff0c;卡密交易系统。 搭建环境Nginx1.22 mysql 5.7 php8.1 rids 7.2 安装方法…

避坑——Matlab c# 联合编程——Native

相同的库&#xff0c;Matlab生成供.net调用的库时会有两套&#xff0c;也就是Native&#xff08;本地&#xff09;&#xff0c;两套库各有优缺点&#xff0c;这这里就不说了&#xff0c;可以翻看网上其他博文 主要是MWStructArray&#xff0c;MWArray等数据交换对象有两套&…

C语言:qsort的使用方法

目录 1. qsort是什么&#xff1f; 2. 为什么要使用qsort 3. qsort的使用 3.1 qsort的返回值和参数 3.2 qsort的compare函数参数 3.3 int类型数组的qsort完整代码 4. qsort完整代码 1. qsort是什么&#xff1f; qsort中的q在英语中是quick&#xff0c;快速的意思了&#…

Platformview在iOS与Android上的实现方式对比

Android中早期版本Platformview的实现基于Virtual Display。VirtualDisplay方案的原理是&#xff0c;先将Native View绘制到虚显&#xff0c;然后Flutter通过从虚显输出中获取纹理并将其与自己内部的widget树进行合成&#xff0c;最后作为Flutter在 Android 上更大的纹理输出的…

【经验】f-string 的一些点

【经验】f-string 的一些点 省几个字别数错了对齐它现在几点 省几个字 让 f-string 给你写表达式&#xff0c;在 f-string 中使用 来自动打印表达式 a 10 b 25 print(f"{a b }") >>> a b 35别数错了 对于过大的数字难以一眼看出来它的数量级&#xf…

Access AR Foundation 5.1 in Unity 2022

如果已经下载安装了ARF但版本是5.0.7 可以通过下面的方式修改 修改后面的数字会自动更新 更新完成后查看版本 官方文档 Access AR Foundation 5.1 in Unity 2021 | AR Foundation | 5.1.2

93. 通用防重幂等设计

文章目录 一、防重与幂等的区别二、幂等性的应用场景三、幂等性与防重关系四、处理流程 一、防重与幂等的区别 防重与幂等是在 Web 应用程序和分布式系统中重要而又非常常见的问题。 防重 防重是指在多次提交同样的请求过程中&#xff0c;系统会检测和消除重复的数据&#xf…

网络安全: Kali Linux 使用 docker-compose 部署 openvas

目录 一、实验 1.环境 2.Kali Linux 安装docker与docker-compose 3.Kali Linux 使用docker-compose方式部署 openvas 4. KaliLinux 使用openvas 二、问题 1. 信息安全漏洞库 2.信息安全漏洞共享平台 3.Windows 更新指南与查询 4.CVE 查询 5.docker-compose 如何修改o…