Skip to content
This repository was archived by the owner on Feb 10, 2025. It is now read-only.

Commit 37feb85

Browse files
committed
chore: virtual scroll
1 parent 13c6f86 commit 37feb85

File tree

10 files changed

+23850
-134
lines changed

10 files changed

+23850
-134
lines changed

dev/App.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
:language="language"
3636
:prev="prev"
3737
:current="current"
38+
:virtual-scroll="{ height: 500, lineMinHeight: 24 }"
3839
/>
3940
</section>
4041
</div>

package-lock.json

Lines changed: 23579 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@
3636
},
3737
"dependencies": {
3838
"diff-match-patch": "^1.0.5",
39-
"highlight.js": "^10.5.0"
39+
"highlight.js": "^10.5.0",
40+
"lodash-es": "^4.17.21"
4041
},
4142
"devDependencies": {
4243
"@babel/plugin-transform-runtime": "^7.12.1",
@@ -49,6 +50,7 @@
4950
"@types/diff-match-patch": "^1.0.32",
5051
"@types/highlight.js": "^10.1.0",
5152
"@types/jest": "^24.0.19",
53+
"@types/lodash-es": "^4.17.4",
5254
"@typescript-eslint/eslint-plugin": "^2.33.0",
5355
"@typescript-eslint/parser": "^2.33.0",
5456
"@vitejs/plugin-vue": "^1.0.5",

src/Code.vue

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
</template>
44

55
<script lang="ts">
6-
import { defineComponent, ref, onMounted, watch } from 'vue'
6+
import { defineComponent, ref, onMounted, watch, nextTick } from 'vue'
77
import { setHighlightCode } from './utils'
88
99
export default defineComponent({
@@ -17,7 +17,8 @@ export default defineComponent({
1717
required: true
1818
}
1919
},
20-
setup (props) {
20+
emits: ['rendered'],
21+
setup (props, { emit }) {
2122
const highlightCode = ref('')
2223
2324
onMounted(() => {
@@ -27,6 +28,8 @@ export default defineComponent({
2728
language: props.language,
2829
code: props.code
2930
})
31+
32+
nextTick(() => emit('rendered'))
3033
}, { immediate: true })
3134
})
3235

src/Diff.vue

Lines changed: 128 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,52 @@
11
<template>
2-
<div class="vue-diff-wrapper" :class="`vue-diff-mode-${mode} vue-diff-theme-${theme}`">
3-
<div ref="viewer" class="vue-diff-viewer">
4-
<table>
5-
<tbody>
6-
<Line
7-
:key="index"
8-
v-for="(data, index) in lines"
9-
:mode="mode"
10-
:language="language"
11-
:data="data"
12-
/>
13-
</tbody>
14-
</table>
2+
<div
3+
class="vue-diff-wrapper"
4+
:class="`vue-diff-mode-${mode} vue-diff-theme-${theme}`"
5+
>
6+
<div
7+
ref="viewer"
8+
class="vue-diff-viewer"
9+
:style="{ height: viewerHeight ? viewerHeight + 'px' : undefined }"
10+
>
11+
<div
12+
class="vue-diff-viewer-inner"
13+
:style="{ minHeight: innerMinHeight ? innerMinHeight + 'px' : undefined }"
14+
>
15+
<Line
16+
:key="index"
17+
v-for="(data, index) in visibleData"
18+
:index="index"
19+
:mode="mode"
20+
:language="language"
21+
:data="data"
22+
:virtualScroll="virtualScroll"
23+
@set-line-height="setLineHeight"
24+
/>
25+
</div>
1526
</div>
1627
</div>
1728
</template>
1829

1930
<script lang="ts">
20-
import { defineComponent, PropType, ref, watch } from 'vue'
21-
import { renderLines } from './utils'
31+
import { defineComponent, PropType, ref, watch, onMounted, onBeforeUnmount, computed, readonly, toRaw } from 'vue'
32+
import throttle from 'lodash-es/throttle'
33+
import { renderLines, renderWords } from './utils'
2234
import Line from './Line.vue'
2335
2436
import type { Mode, Theme, Lines } from './utils'
2537
38+
interface VirtualScroll {
39+
height?: number;
40+
lineMinHeight?: number;
41+
}
42+
43+
interface Data {
44+
key: number;
45+
render: Lines;
46+
top?: number;
47+
height?: number;
48+
}
49+
2650
export default defineComponent({
2751
components: {
2852
Line
@@ -47,26 +71,103 @@ export default defineComponent({
4771
current: {
4872
type: String,
4973
default: ''
74+
},
75+
virtualScroll: {
76+
type: false || Object as PropType<VirtualScroll>,
77+
default: false
5078
}
5179
},
5280
setup (props) {
53-
const lines = ref<Array<Lines>>([])
81+
const viewer = ref<null|HTMLElement>(null)
82+
const source = ref<Array<Data>>([])
83+
const visibleData = ref<Array<Data>>([])
84+
const heightList = ref<Array<number>>([])
85+
86+
const viewerHeight = computed(() => {
87+
if (!props.virtualScroll) return false
88+
return props.virtualScroll.height || 500
89+
})
90+
91+
const innerMinHeight = computed(() => {
92+
if (!props.virtualScroll) return false
93+
return heightList.value.reduce((acc, curr) => acc + curr, 0)
94+
})
95+
96+
const getVisibleData = () => {
97+
if (!viewer.value || !viewerHeight.value) return []
98+
99+
const result: Data[] = []
100+
const start = viewer.value.scrollTop - viewerHeight.value
101+
const end = viewer.value.scrollTop + viewerHeight.value * 2
102+
103+
heightList.value.reduce((acc, curr, index: number) => {
104+
if (acc >= start && acc <= end) {
105+
result.push({
106+
key: index,
107+
render: source.value[index].render,
108+
top: acc,
109+
height: 24
110+
})
111+
}
112+
113+
return acc + curr
114+
}, 0)
54115
55-
watch([
56-
() => props.mode,
57-
() => props.prev,
58-
() => props.current
59-
], () => {
60-
const render = renderLines(props.mode, props.prev, props.current)
116+
return result
117+
}
118+
119+
const handleScroll = () => {
120+
visibleData.value = getVisibleData()
121+
}
61122
62-
if (render.length > 1000) {
63-
console.warn('Comparison of many lines is not recommended because rendering delays occur.')
123+
const setLineHeight = (index: number, height: number) => {
124+
const item = source.value.find(item => item.key === index)
125+
if (item) {
126+
item.height = height
64127
}
128+
}
129+
130+
onMounted(() => {
131+
if (!viewer.value) return
132+
viewer.value.addEventListener('scroll', throttle(handleScroll, 500))
65133
66-
lines.value = render
67-
}, { immediate: true })
134+
watch([
135+
() => props.mode,
136+
() => props.prev,
137+
() => props.current
138+
], () => {
139+
source.value = renderLines(props.mode, props.prev, props.current).map((render, index) => {
140+
return {
141+
key: index,
142+
render,
143+
height: 24
144+
}
145+
})
68146
69-
return { lines }
147+
if (!props.virtualScroll) {
148+
if (source.value.length > 500) {
149+
console.warn('If there are many lines, please use virtualScroll property.')
150+
}
151+
152+
visibleData.value = source.value
153+
} else {
154+
visibleData.value = getVisibleData()
155+
}
156+
}, { immediate: true })
157+
})
158+
159+
onBeforeUnmount(() => {
160+
if (!viewer.value) return
161+
viewer.value.removeEventListener('scroll', throttle(handleScroll, 500))
162+
})
163+
164+
return {
165+
visibleData,
166+
viewer,
167+
viewerHeight,
168+
innerMinHeight,
169+
setLineHeight
170+
}
70171
}
71172
})
72173
</script>

src/Line.vue

Lines changed: 59 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,68 @@
11
<template>
2-
<!-- split view -->
3-
<tr v-if="mode === 'split'" :class="`vue-diff-row-${mode}`">
4-
<template :key="index" v-for="(line, index) in data">
5-
<td class="lineNum" :class="`vue-diff-cell-${line.type}`">
6-
{{ line.lineNum }}
7-
</td>
8-
<td class="code" :class="`vue-diff-cell-${line.type}`">
9-
<Code
10-
:language="language"
11-
:code="setCode(line, data, index)"
12-
/>
13-
</td>
2+
<div
3+
ref="line"
4+
class="vue-diff-row"
5+
:class="`vue-diff-row-${mode}`"
6+
:style="virtualScroll ? {
7+
position: 'absoltue',
8+
minHeight: virtualScroll.minHeight + 'px'
9+
} : undefined">
10+
<!-- split view -->
11+
<template v-if="mode === 'split'">
12+
<template :key="index" v-for="(line, index) in data.render">
13+
<div class="lineNum" :class="`vue-diff-cell-${line.type}`">
14+
{{ line.lineNum }}
15+
</div>
16+
<div class="code" :class="`vue-diff-cell-${line.type}`">
17+
<Code
18+
:language="language"
19+
:code="setCode(line, data.render, index)"
20+
@rendered="rendered"
21+
/>
22+
</div>
23+
</template>
1424
</template>
15-
</tr>
16-
<!-- // split view -->
17-
<!-- unified view -->
18-
<template v-if="mode === 'unified'">
19-
<tr :class="`vue-diff-row-${mode}`">
20-
<td class="lineNum" :class="`vue-diff-cell-${data[0].type}`">
21-
{{ data[0].lineNum }}
22-
</td>
23-
<td class="code" :class="`vue-diff-cell-${data[0].type}`">
25+
<!-- // split view -->
26+
<!-- unified view -->
27+
<template v-if="mode === 'unified'">
28+
<div class="lineNum" :class="`vue-diff-cell-${data.render[0].type}`">
29+
{{ data.render[0].lineNum }}
30+
</div>
31+
<div class="code" :class="`vue-diff-cell-${data.render[0].type}`">
2432
<Code
2533
:language="language"
26-
:code="setCode(data[0])"
34+
:code="setCode(data.render[0])"
35+
@rendered="rendered"
2736
/>
28-
</td>
29-
</tr>
30-
</template>
31-
<!-- // unified view -->
37+
</div>
38+
</template>
39+
<!-- // unified view -->
40+
</div>
3241
</template>
3342

3443
<script lang="ts">
35-
import { defineComponent, PropType } from 'vue'
44+
import { defineComponent, PropType, watch, ref } from 'vue'
3645
import Code from './Code.vue'
3746
import { renderWords } from './utils'
3847
3948
import type { Mode, Lines, Line } from './utils'
4049
50+
interface Data {
51+
key: number;
52+
render: Lines;
53+
top?: number;
54+
height?: number;
55+
}
56+
4157
export default defineComponent({
4258
components: {
4359
Code
4460
},
4561
props: {
62+
index: {
63+
type: Number,
64+
required: true
65+
},
4666
mode: {
4767
type: String as PropType<Mode>,
4868
required: true
@@ -54,9 +74,13 @@ export default defineComponent({
5474
data: {
5575
type: Object as PropType<Lines>,
5676
required: true
57-
}
77+
},
78+
virtualScroll: {},
79+
lineInfo: {}
5880
},
59-
setup () {
81+
emits: ['setLineHeight'],
82+
setup (props, { emit }) {
83+
const line = ref<null|HTMLElement>(null)
6084
const setCode = (line: Line, data?: Lines, index?: number) => {
6185
if (!line.value) return '\n'
6286
@@ -72,7 +96,12 @@ export default defineComponent({
7296
return renderWords(differ.value, line.value) // render with modified tags
7397
}
7498
75-
return { setCode }
99+
const rendered = () => {
100+
if (!line.value) return
101+
emit('setLineHeight', props.index, line.value.offsetHeight)
102+
}
103+
104+
return { line, setCode, rendered }
76105
}
77106
})
78107
</script>

src/shims-vue.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
declare module 'lodash-es/throttle'
12
declare module 'highlight.js/lib/core'
23
declare module 'highlight.js/lib/languages/css'
34
declare module 'highlight.js/lib/languages/xml'

0 commit comments

Comments
 (0)