和各大網(wǎng)站做視頻的工作谷歌優(yōu)化方法
前言
由于svelte solid 兩大無虛擬DOM框架,由于其性能好,在前端越來越有影響力。
因此本次想要驗證,這三個框架關(guān)于實現(xiàn)表格虛擬滾動的性能。
比較版本
- vue@3.4.21
- svelte@4.2.12
- solid-js@1.8.15
比較代碼
這里使用了我的 stk-table-vue(npm)?中實現(xiàn)虛擬滾動主要代碼。
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, // 數(shù)組開始位置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無頭時,需要上下各預(yù)渲染一行。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();
})/*** 初始化虛擬滾動參數(shù)* @param {number} [height] 虛擬滾動的高度*/
function initVirtualScroll(height) {initVirtualScrollY(height);// this.initVirtualScrollX();
}
/*** 初始化Y虛擬滾動參數(shù)* @param {number} [height] 虛擬滾動的高度*/
function initVirtualScrollY(height) {if (virtual_on.value) {virtualScroll.value.containerHeight = typeof height === 'number' ? height : tableContainer.value?.offsetHeight;updateVirtualScrollY(tableContainer.value?.scrollTop);}
}
/** 通過滾動條位置,計算虛擬滾動的參數(shù) */
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;// 此處可優(yōu)化,因為訪問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, // 數(shù)組開始位置rowHeight: 28,offsetTop: 0, // 表格定位上邊距scrollTop: 0, // 縱向滾動條位置,用于判斷是橫向滾動還是縱向};export let columns = [{ dataIndex: 'id', title: 'ID' },{ dataIndex: 'name', title: 'Name' },];export let dataSource = [];$: dataSourceCopy = [...dataSource];/** 數(shù)據(jù)量大于2頁才開始虛擬滾動*//** 虛擬滾動展示的行數(shù) */$: virtual_pageSize = Math.ceil(virtualScroll.containerHeight / virtualScroll.rowHeight) + 1; // 這里最終+1,因為headless=true無頭時,需要上下各預(yù)渲染一行。$: 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();});/*** 初始化虛擬滾動參數(shù)* @param {number} [height] 虛擬滾動的高度*/function initVirtualScroll(height) {initVirtualScrollY(height);// this.initVirtualScrollX();}/*** 初始化Y虛擬滾動參數(shù)* @param {number} [height] 虛擬滾動的高度*/function initVirtualScrollY(height) {if(virtual_on){virtualScroll.containerHeight = typeof height === 'number' ? height : tableContainer?.offsetHeight;virtualScroll = virtualScroll;updateVirtualScrollY(tableContainer?.scrollTop);}}/** 通過滾動條位置,計算虛擬滾動的參數(shù) */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;// 此處可優(yōu)化,因為訪問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, // 數(shù)組開始位置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無頭時,需要上下各預(yù)渲染一行。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();});/*** 初始化虛擬滾動參數(shù)* @param {number} [height] 虛擬滾動的高度*/function initVirtualScroll(height) {initVirtualScrollY(height);// this.initVirtualScrollX();}/*** 初始化Y虛擬滾動參數(shù)* @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);}}/** 通過滾動條位置,計算虛擬滾動的參數(shù) */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});// 必須擴展運算,否則不觸發(fā)更新}function onTableScroll(e) {if (!e?.target) return;// 此處可優(yōu)化,因為訪問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速度調(diào)至減速6x
觀察瀏覽器開發(fā)者面板performance標(biāo)簽下的任務(wù)耗時。
vue任務(wù)
svelte任務(wù)
solid任務(wù)
結(jié)論
觀察solid任務(wù)的超時情況(紅色部分),大體比vue與svelte要少。
solid與svelte 相較vue在虛擬滾動情況下,未能觀察到明顯優(yōu)勢。