Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(split): Added Delay Rendering #6805

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.en-US.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# CHANGELOG

### Features

- 为 Split组件添加了 lazy 延迟渲染属性

## 2.41.0

`2025-01-05`
6 changes: 6 additions & 0 deletions CHANGELOG.zh-CN.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# CHANGELOG

## NEXT_VERSION

### Features

- 为 Split组件添加了 lazy 延迟渲染属性

## 2.41.0

`2025-01-05`
2 changes: 2 additions & 0 deletions src/split/demos/enUS/index.demo-entry.md
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@ nest.vue
event.vue
slot.vue
controlled.vue
lazy.vue
```

## API
@@ -32,6 +33,7 @@ controlled.vue
| pane2-style | `Object \| string` | `undefined` | The Style of the second pane | 2.38.2 |
| resize-trigger-size | `number` | `3` | Size of the resize trigger. | 2.36.0 |
| size | `string \| number` | `undefined` | Split is the controlled split size, when it's `number` it should in 0 ~ 1, when it's `string` it should be formatted in `${number}px`. | 2.38.0, `string` 2.38.2 |
| lazy | `boolean` | `false` | Delay rendering | NEXT_VERSION |
| watch-props | `Array<'defaultSize'>` | `undefined` | Default prop names that needed to be watched. Components will be updated after the prop is changed. Note: the `watch-props` itself is not reactive. | 2.38.0 |
| on-drag-start | `(e: Event) => void` | `undefined` | Callback function when drag start. | 2.36.0 |
| on-drag-move | `(e: Event) => void` | `undefined` | Callback function when dragging. | 2.36.0 |
53 changes: 53 additions & 0 deletions src/split/demos/enUS/lazy.demo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<markdown>
# Delay rendering mode
When you have too much content, dragging and dropping frequently triggers element text re-arrangement/style re-painting, it will be very stuttering and affecting the user experience.
It can be optimized by delayed rendering and not updated until the drag and drop is finished.
Turn on delayed rendering by setting `lazy` to `true`.
</markdown>

<script setup lang="ts">
import { NSplit } from 'naive-ui'
import { ref } from 'vue'

const split = ref<number>(0.8)
</script>

<template>
<n-flex vertical>
<NSplit v-model:size="split" lazy style="height: 200px">
<template #1>
<div style="height: 100%; overflow: auto">
In front-end development, MathJax is a widely used library for
rendering mathematical formulas in web pages. because MathJax Rendered
mathematical formulas are usually generated dynamically, so in actual
use, the size of the container may change, which triggers the
browser's redraw process. Caused by changes in container size CSS
redrawing has a certain impact on performance, especially when pages
are updated frequently or formula content is complex. The MathJax
rendering process includes parsing LaTeX or MathML Mathematical
formulas of formats that convert to a browser renderable format (such
as HTML, SVG or MathML). In this process, MathJax The final display
effect of the formula will be calculated based on the size of the
container. If the size of the container changes (e.g., browser window
resize or some DOM element changed size), MathJax The layout of the
formula is recalculated, triggering the browser's repaint. CSS Repaint
is when an element's visual representation (such as size, color, or
layout) changes, the browser needs to recalculate and repaint the
element. This can lead to performance degradation, especially when
dealing with large numbers of formulas or complex pages. To reduce
performance losses, it can be optimized by avoiding frequent changes
in container size and using appropriate rendering modes (e.g. SVG
rendering is usually more efficient than HTML rendering) and uses CSS
reasonably Animation or transition effects, reducing the number of
redrawing and rearrangement. In short, front-end developers need to be
aware of MathJax The relationship between the rendering process and
the changes in container size, reasonably optimize the code, and
ensure stable page performance.
</div>
</template>
<template #2>
Formula rendering scenes, this may be required
</template>
</NSplit>
</n-flex>
</template>
2 changes: 2 additions & 0 deletions src/split/demos/zhCN/index.demo-entry.md
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@ event.vue
slot.vue
controlled.vue
pixel-value.vue
lazy.vue
```

## API
@@ -33,6 +34,7 @@ pixel-value.vue
| pane2-style | `Object \| string` | `undefined` | 第二个面板的样式 | 2.38.2 |
| resize-trigger-size | `number` | `3` | Split 的分隔条大小 | 2.36.0 |
| size | `string \| number` | `undefined` | Split 的受控分割大小,为 `number` 类型时应该为 0 ~ 1 之间的值,为 `string` 类型时应该为 `${number}px` 格式 | 2.38.0, `string` 2.38.2 |
| lazy | `boolean` | `false` | 延迟渲染 | NEXT_VERSION |
| watch-props | `Array<'defaultSize'>` | `undefined` | 需要检测变更的默认属性,检测后组件状态会更新。注意:`watch-props` 本身不是响应式的 | 2.38.0 |
| on-drag-start | `(e: Event) => void` | `undefined` | 开始拖拽的回调函数 | 2.36.0 |
| on-drag-move | `(e: Event) => void` | `undefined` | 拖拽中的回调函数 | 2.36.0 |
42 changes: 42 additions & 0 deletions src/split/demos/zhCN/lazy.demo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<markdown>
# 延迟渲染模式
当你内容太多的时候, 拖拽频繁触发元素文字重排 / 样式重绘时, 会很卡顿,影响用户体验。
可以通过延迟渲染进行优化,直到拖拽结束才更新。
通过设置 `lazy` 为 `true` 来开启延迟渲染。
</markdown>

<script setup lang="ts">
import { NSplit } from 'naive-ui'
import { ref } from 'vue'

const split = ref<number>(0.8)
</script>

<template>
<n-flex vertical>
<NSplit v-model:size="split" lazy style="height: 200px">
<template #1>
<div style="height: 100%; overflow: auto">
在前端开发中,MathJax
是一个广泛使用的库,用于在网页中渲染数学公式。由于 MathJax
渲染的数学公式通常是动态生成的,因此在实际使用中,容器的大小可能发生变化,进而触发浏览器的重绘过程。容器大小变化引起的
CSS 重绘对性能有一定的影响,尤其是在页面频繁更新或者公式内容复杂时。
MathJax 渲染过程包括解析 LaTeX 或 MathML
格式的数学公式,将其转换为浏览器可渲染的格式(如 HTML、SVG 或
MathML)。在这个过程中,MathJax
会根据容器的大小来计算公式的最终显示效果。如果容器的大小发生变化(例如,浏览器窗口调整或某些
DOM 元素改变了尺寸),MathJax
会重新计算公式的布局,从而触发浏览器的重绘。 CSS
重绘是指当元素的视觉表现(如尺寸、颜色或布局)发生变化时,浏览器需要重新计算并重新绘制该元素。这可能会导致性能下降,尤其是在处理大量公式或复杂页面时。为了减小性能损耗,可以通过以下方式优化:避免频繁改变容器的尺寸,使用合适的渲染模式(如
SVG 渲染通常比 HTML 渲染更高效),并合理运用 CSS
动画或过渡效果,减少重绘和重排的次数。 总之,前端开发者需要意识到
MathJax
渲染过程与容器大小变化之间的关系,合理优化代码,确保页面性能稳定。
</div>
</template>
<template #2>
公式渲染场景, 可能会需要用到这个
</template>
</NSplit>
</n-flex>
</template>
67 changes: 58 additions & 9 deletions src/split/src/Split.tsx
Original file line number Diff line number Diff line change
@@ -41,6 +41,10 @@ export const splitProps = {
onUpdateSize: [Function, Array] as PropType<
SplitOnUpdateSize | SplitOnUpdateSize[]
>,
lazy: {
type: Boolean as PropType<boolean>,
default: false
},
size: [String, Number] as PropType<string | number>,
min: {
type: [String, Number] as PropType<string | number>,
@@ -102,7 +106,7 @@ export default defineComponent({
if (props.watchProps?.includes('defaultSize')) {
watchEffect(() => (uncontrolledSizeRef.value = props.defaultSize))
}
// use to update controlled or uncontrolled values
// Update controlled or uncontrolled size values
const doUpdateSize = (size: number | string): void => {
const _onUpdateSize = props['onUpdate:size']
if (props.onUpdateSize)
@@ -112,6 +116,10 @@ export default defineComponent({
uncontrolledSizeRef.value = size
}
const mergedSizeRef = useMergedState(controlledSizeRef, uncontrolledSizeRef)
// When lazy is true, save the new size to pendingSizeRef during dragging
const pendingSizeRef = ref(mergedSizeRef.value)
// Styles for real-time display of drag and drop indicator lines
const indicatorStyle = ref<CSSProperties>({})

const firstPaneStyle = computed(() => {
const sizeValue = mergedSizeRef.value
@@ -164,7 +172,12 @@ export default defineComponent({
if (props.onDragMove)
props.onDragMove(e)
}
const onMouseUp = (): void => {
const onMouseUp = (e: MouseEvent): void => {
// Update the size at the end of dragging in lazy mode and clear the indicator line
if (props.lazy) {
doUpdateSize(pendingSizeRef.value)
indicatorStyle.value = {}
}
off(mouseMoveEvent, document, onMouseMove)
off(mouseUpEvent, document, onMouseUp)
isDraggingRef.value = false
@@ -211,7 +224,6 @@ export default defineComponent({
: event.clientY - containerRect.top + offset

const { min, max } = props

const pxMin
= typeof min === 'string' ? depx(min) : min * containerUsableSize
const pxMax
@@ -220,13 +232,41 @@ export default defineComponent({
let nextPxSize = newPxSize
nextPxSize = Math.max(nextPxSize, pxMin)
nextPxSize = Math.min(nextPxSize, pxMax, containerUsableSize)
// in pixel mode

let newSize: number | string
if (typeof mergedSizeRef.value === 'string') {
doUpdateSize(`${nextPxSize}px`)
newSize = `${nextPxSize}px`
}
else {
newSize = nextPxSize / containerUsableSize
}
// Judging from lazy whether to update immediately
if (props.lazy) {
pendingSizeRef.value = newSize
// Update the indicator line style, the indicator line follows the mouse
if (direction === 'horizontal') {
indicatorStyle.value = {
position: 'absolute',
left: `${nextPxSize}px`,
top: '0',
bottom: '0',
width: '1px',
background: cssVarsRef.value['--n-resize-trigger-color-hover']
}
}
else {
indicatorStyle.value = {
position: 'absolute',
top: `${nextPxSize}px`,
left: '0',
right: '0',
height: '1px',
background: cssVarsRef.value['--n-resize-trigger-color-hover']
}
}
}
else {
// in percentage mode
doUpdateSize(nextPxSize / containerUsableSize)
doUpdateSize(newSize)
}
}

@@ -244,7 +284,9 @@ export default defineComponent({
resizeTriggerWrapperStyle,
resizeTriggerStyle,
handleMouseDown,
firstPaneStyle
firstPaneStyle,
indicatorStyle,
lazy: props.lazy
}
},
render() {
@@ -256,7 +298,7 @@ export default defineComponent({
`${this.mergedClsPrefix}-split--${this.direction}`,
this.themeClass
]}
style={this.cssVars as CSSProperties}
style={[this.cssVars as CSSProperties, { position: 'relative' }]}
>
<div
class={[`${this.mergedClsPrefix}-split-pane-1`, this.pane1Class]}
@@ -284,6 +326,13 @@ export default defineComponent({
])}
</div>
)}
{this.lazy && this.isDragging && (
<div
class={`${this.mergedClsPrefix}-split__resize-indicator`}
style={this.indicatorStyle}
>
</div>
)}
<div
class={[`${this.mergedClsPrefix}-split-pane-2`, this.pane2Class]}
style={this.pane2Style}