Optimise scrolling and virtual rendering performance
This commit is contained in:
parent
4d3f669ae7
commit
0eadca9acb
|
@ -35,7 +35,8 @@
|
||||||
const tableId = writable(table?.tableId)
|
const tableId = writable(table?.tableId)
|
||||||
const changeCache = writable({})
|
const changeCache = writable({})
|
||||||
const newRows = writable([])
|
const newRows = writable([])
|
||||||
const visibleCells = writable({ y: [0, 0], x: [0, 0] })
|
const visibleRows = writable([0, 0])
|
||||||
|
const visibleColumns = writable([0, 0])
|
||||||
|
|
||||||
// Build up spreadsheet context and additional stores
|
// Build up spreadsheet context and additional stores
|
||||||
const context = {
|
const context = {
|
||||||
|
@ -49,7 +50,8 @@
|
||||||
changeCache,
|
changeCache,
|
||||||
newRows,
|
newRows,
|
||||||
cellHeight,
|
cellHeight,
|
||||||
visibleCells,
|
visibleRows,
|
||||||
|
visibleColumns,
|
||||||
}
|
}
|
||||||
const { reorder, reorderPlaceholder } = createReorderStores(context)
|
const { reorder, reorderPlaceholder } = createReorderStores(context)
|
||||||
const resize = createResizeStore(context)
|
const resize = createResizeStore(context)
|
||||||
|
@ -67,7 +69,7 @@
|
||||||
$: rowCount = $rows.length
|
$: rowCount = $rows.length
|
||||||
$: selectedRowCount = Object.values($selectedRows).filter(x => !!x).length
|
$: selectedRowCount = Object.values($selectedRows).filter(x => !!x).length
|
||||||
$: updateSortedRows($fetch, $newRows)
|
$: updateSortedRows($fetch, $newRows)
|
||||||
$: visibleRows = $rows.slice($visibleCells.y[0], $visibleCells.y[1])
|
$: renderedRows = $rows.slice($visibleRows[0], $visibleRows[1])
|
||||||
|
|
||||||
const createFetch = datasource => {
|
const createFetch = datasource => {
|
||||||
return fetchData({
|
return fetchData({
|
||||||
|
@ -160,16 +162,16 @@
|
||||||
|
|
||||||
const updateSortedRows = (unsortedRows, newRows) => {
|
const updateSortedRows = (unsortedRows, newRows) => {
|
||||||
let foo = unsortedRows.rows
|
let foo = unsortedRows.rows
|
||||||
for (let i = 0; i < 10; i++) {
|
for (let i = 0; i < 3; i++) {
|
||||||
foo = foo.concat(foo.map(x => ({ ...x, _id: x._id + "x" })))
|
foo = foo.concat(foo.map(x => ({ ...x, _id: x._id + "x" })))
|
||||||
}
|
}
|
||||||
// let sortedRows = foo.slice()
|
let sortedRows = foo.slice()
|
||||||
// sortedRows.sort((a, b) => {
|
sortedRows.sort((a, b) => {
|
||||||
// const aIndex = newRows.indexOf(a._id)
|
const aIndex = newRows.indexOf(a._id)
|
||||||
// const bIndex = newRows.indexOf(b._id)
|
const bIndex = newRows.indexOf(b._id)
|
||||||
// return aIndex < bIndex ? -1 : 1
|
return aIndex < bIndex ? -1 : 1
|
||||||
// })
|
})
|
||||||
$rows = foo.slice()
|
$rows = sortedRows
|
||||||
}
|
}
|
||||||
|
|
||||||
// API for children to consume
|
// API for children to consume
|
||||||
|
@ -229,8 +231,8 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- All real rows -->
|
<!-- All real rows -->
|
||||||
{#each visibleRows as row, rowIdx (row._id)}
|
{#each renderedRows as row, rowIdx (row._id)}
|
||||||
<SpreadsheetRow {row} rowIdx={rowIdx + $visibleCells.y[0]} />
|
<SpreadsheetRow {row} rowIdx={rowIdx + $visibleRows[0]} />
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
<!-- New row placeholder -->
|
<!-- New row placeholder -->
|
||||||
|
|
|
@ -1,8 +1,16 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext, onMount } from "svelte"
|
import { getContext, onMount } from "svelte"
|
||||||
|
import { domDebounce } from "../../../utils/domDebounce"
|
||||||
|
|
||||||
const { columns, selectedCellId, rand, visibleCells, cellHeight, rows } =
|
const {
|
||||||
getContext("spreadsheet")
|
columns,
|
||||||
|
selectedCellId,
|
||||||
|
rand,
|
||||||
|
visibleRows,
|
||||||
|
visibleColumns,
|
||||||
|
cellHeight,
|
||||||
|
rows,
|
||||||
|
} = getContext("spreadsheet")
|
||||||
|
|
||||||
const padding = 180
|
const padding = 180
|
||||||
|
|
||||||
|
@ -12,7 +20,8 @@
|
||||||
let scrollLeft = 0
|
let scrollLeft = 0
|
||||||
let scrollTop = 0
|
let scrollTop = 0
|
||||||
|
|
||||||
$: computeVisibleCells($columns, scrollLeft, scrollTop, width, height)
|
$: updateVisibleRows($columns, scrollTop, height)
|
||||||
|
$: updateVisibleColumns($columns, scrollLeft, width)
|
||||||
$: contentHeight = ($rows.length + 2) * cellHeight + padding
|
$: contentHeight = ($rows.length + 2) * cellHeight + padding
|
||||||
$: contentWidth = computeContentWidth($columns)
|
$: contentWidth = computeContentWidth($columns)
|
||||||
$: horizontallyScrolled = scrollLeft > 0
|
$: horizontallyScrolled = scrollLeft > 0
|
||||||
|
@ -26,35 +35,52 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the current scroll position
|
// Store the current scroll position
|
||||||
const handleScroll = e => {
|
// let lastTop
|
||||||
// Only update scroll offsets when a sizable change happens
|
// let lastLeft
|
||||||
if (Math.abs(scrollTop - e.target.scrollTop) > 10) {
|
// let ticking = false
|
||||||
scrollTop = e.target.scrollTop
|
// const handleScroll = e => {
|
||||||
}
|
// lastTop = e.target.scrollTop
|
||||||
if (Math.abs(scrollLeft - e.target.scrollLeft) > 10) {
|
// lastLeft = e.target.scrollLeft
|
||||||
scrollLeft = e.target.scrollLeft
|
// if (!ticking) {
|
||||||
}
|
// ticking = true
|
||||||
if (e.target.scrollLeft === 0) {
|
// requestAnimationFrame(() => {
|
||||||
scrollLeft = 0
|
// if (Math.abs(lastTop - scrollTop) > 100) {
|
||||||
}
|
// scrollTop = lastTop
|
||||||
}
|
// }
|
||||||
|
// if (lastLeft === 0 || Math.abs(lastLeft - scrollLeft) > 100) {
|
||||||
|
// scrollLeft = lastLeft
|
||||||
|
// }
|
||||||
|
// ticking = false
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
const computeVisibleCells = (
|
const handleScroll = domDebounce(
|
||||||
columns,
|
({ left, top }) => {
|
||||||
scrollLeft,
|
if (Math.abs(top - scrollTop) > 100) {
|
||||||
scrollTop,
|
scrollTop = top
|
||||||
width,
|
}
|
||||||
height
|
if (left === 0 || Math.abs(left - scrollLeft) > 100) {
|
||||||
) => {
|
scrollLeft = left
|
||||||
|
}
|
||||||
|
},
|
||||||
|
e => ({ left: e.target.scrollLeft, top: e.target.scrollTop })
|
||||||
|
)
|
||||||
|
|
||||||
|
const updateVisibleRows = (columns, scrollTop, height) => {
|
||||||
if (!columns.length) {
|
if (!columns.length) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute row visibility
|
// Compute row visibility
|
||||||
const rows = Math.ceil(height / cellHeight) + 8
|
const rows = Math.ceil(height / cellHeight) + 8
|
||||||
const firstRow = Math.max(0, Math.floor(scrollTop / cellHeight) - 4)
|
const firstRow = Math.max(0, Math.floor(scrollTop / cellHeight) - 4)
|
||||||
const visibleRows = [firstRow, firstRow + rows]
|
visibleRows.set([firstRow, firstRow + rows])
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateVisibleColumns = (columns, scrollLeft, width) => {
|
||||||
|
if (!columns.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
// Compute column visibility
|
// Compute column visibility
|
||||||
let startColIdx = 1
|
let startColIdx = 1
|
||||||
let rightEdge = columns[1].width
|
let rightEdge = columns[1].width
|
||||||
|
@ -68,12 +94,7 @@
|
||||||
leftEdge += columns[endColIdx]?.width
|
leftEdge += columns[endColIdx]?.width
|
||||||
endColIdx++
|
endColIdx++
|
||||||
}
|
}
|
||||||
const visibleColumns = [Math.max(1, startColIdx - 2), endColIdx + 2]
|
visibleColumns.set([Math.max(1, startColIdx - 2), endColIdx + 2])
|
||||||
|
|
||||||
visibleCells.set({
|
|
||||||
x: visibleColumns,
|
|
||||||
y: visibleRows,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
@ -87,26 +108,15 @@
|
||||||
observer.disconnect()
|
observer.disconnect()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
let sheetStyles = ""
|
|
||||||
let left = 0
|
|
||||||
for (let i = 0; i < 20; i++) {
|
|
||||||
if (i === 1) {
|
|
||||||
left += 40
|
|
||||||
}
|
|
||||||
sheetStyles += `--col-${i}-width:${160}px; --col-${i}-left:${left}px;`
|
|
||||||
left += 160
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
bind:this={ref}
|
bind:this={ref}
|
||||||
class="spreadsheet"
|
class="spreadsheet"
|
||||||
class:horizontally-scrolled={horizontallyScrolled}
|
class:horizontally-scrolled={horizontallyScrolled}
|
||||||
on:scroll={handleScroll}
|
|
||||||
on:click|self={() => ($selectedCellId = null)}
|
on:click|self={() => ($selectedCellId = null)}
|
||||||
id={`sheet-${rand}-body`}
|
id={`sheet-${rand}-body`}
|
||||||
style={sheetStyles}
|
on:scroll={handleScroll}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="content"
|
class="content"
|
||||||
|
@ -119,10 +129,10 @@
|
||||||
<style>
|
<style>
|
||||||
.spreadsheet {
|
.spreadsheet {
|
||||||
display: block;
|
display: block;
|
||||||
overflow: auto;
|
|
||||||
height: 800px;
|
height: 800px;
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
.content {
|
.content {
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import SpreadsheetCell from "./SpreadsheetCell.svelte"
|
import SpreadsheetCell from "./SpreadsheetCell.svelte"
|
||||||
import SpacerCell from "./SpacerCell.svelte"
|
|
||||||
import OptionsCell from "./cells/OptionsCell.svelte"
|
import OptionsCell from "./cells/OptionsCell.svelte"
|
||||||
import DateCell from "./cells/DateCell.svelte"
|
import DateCell from "./cells/DateCell.svelte"
|
||||||
import MultiSelectCell from "./cells/MultiSelectCell.svelte"
|
import MultiSelectCell from "./cells/MultiSelectCell.svelte"
|
||||||
|
@ -20,16 +19,16 @@
|
||||||
selectedRows,
|
selectedRows,
|
||||||
changeCache,
|
changeCache,
|
||||||
spreadsheetAPI,
|
spreadsheetAPI,
|
||||||
visibleCells,
|
visibleColumns,
|
||||||
cellHeight,
|
cellHeight,
|
||||||
} = getContext("spreadsheet")
|
} = getContext("spreadsheet")
|
||||||
|
|
||||||
$: rowSelected = !!$selectedRows[row._id]
|
$: rowSelected = !!$selectedRows[row._id]
|
||||||
$: rowHovered = $hoveredRowId === row._id
|
$: rowHovered = $hoveredRowId === row._id
|
||||||
$: data = { ...row, ...$changeCache[row._id] }
|
$: data = { ...row, ...$changeCache[row._id] }
|
||||||
$: visibleColumns = [
|
$: renderedColumns = [
|
||||||
$columns[0],
|
$columns[0],
|
||||||
...$columns.slice($visibleCells.x[0], $visibleCells.x[1]),
|
...$columns.slice($visibleColumns[0], $visibleColumns[1]),
|
||||||
]
|
]
|
||||||
$: containsSelectedCell = $selectedCellId?.split("-")[0] === row._id
|
$: containsSelectedCell = $selectedCellId?.split("-")[0] === row._id
|
||||||
|
|
||||||
|
@ -77,7 +76,7 @@
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
</SpreadsheetCell>
|
</SpreadsheetCell>
|
||||||
{#each visibleColumns as column (column.name)}
|
{#each renderedColumns as column (column.name)}
|
||||||
{@const cellIdx = `${row._id}-${column.name}`}
|
{@const cellIdx = `${row._id}-${column.name}`}
|
||||||
<SpreadsheetCell
|
<SpreadsheetCell
|
||||||
{rowSelected}
|
{rowSelected}
|
||||||
|
@ -116,7 +115,7 @@
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row :global(>:last-child) {
|
.row :global(> :last-child) {
|
||||||
border-right-width: 1px;
|
border-right-width: 1px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
export const domDebounce = callback => {
|
export const domDebounce = (callback, extractParams = x => x) => {
|
||||||
let active = false
|
let active = false
|
||||||
return e => {
|
let lastParams
|
||||||
|
return (...params) => {
|
||||||
|
lastParams = extractParams(...params)
|
||||||
if (!active) {
|
if (!active) {
|
||||||
window.requestAnimationFrame(() => {
|
active = true
|
||||||
callback(e)
|
requestAnimationFrame(() => {
|
||||||
|
callback(lastParams)
|
||||||
active = false
|
active = false
|
||||||
})
|
})
|
||||||
active = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue