[Bug] Fix zoom lag of DOM widget (#3714)

This commit is contained in:
Chenlei Hu
2025-05-02 10:58:51 -04:00
committed by GitHub
parent 53372110d3
commit ec8ee49a2c
4 changed files with 48 additions and 23 deletions

View File

@@ -9,7 +9,9 @@ test.describe('Load Workflow in Media', () => {
'no_workflow.webp', 'no_workflow.webp',
'large_workflow.webp', 'large_workflow.webp',
'workflow.webm', 'workflow.webm',
'workflow.glb', // Skipped due to 3d widget unstable visual result.
// 3d widget shows grid after fully loaded.
// 'workflow.glb',
'workflow.mp4', 'workflow.mp4',
'workflow.mov', 'workflow.mov',
'workflow.m4v', 'workflow.m4v',

View File

@@ -187,7 +187,10 @@ test.describe('Image widget', () => {
}) })
test.describe('Animated image widget', () => { test.describe('Animated image widget', () => {
test('Shows preview of uploaded animated image', async ({ comfyPage }) => { // https://github.com/Comfy-Org/ComfyUI_frontend/issues/3718
test.skip('Shows preview of uploaded animated image', async ({
comfyPage
}) => {
await comfyPage.loadWorkflow('widgets/load_animated_webp') await comfyPage.loadWorkflow('widgets/load_animated_webp')
// Get position of the load animated webp node // Get position of the load animated webp node

View File

@@ -17,7 +17,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useEventListener } from '@vueuse/core' import { useElementBounding, useEventListener } from '@vueuse/core'
import { CSSProperties, computed, onMounted, ref, watch } from 'vue' import { CSSProperties, computed, onMounted, ref, watch } from 'vue'
import { useAbsolutePosition } from '@/composables/element/useAbsolutePosition' import { useAbsolutePosition } from '@/composables/element/useAbsolutePosition'
@@ -38,18 +38,16 @@ const emit = defineEmits<{
const widgetElement = ref<HTMLElement | undefined>() const widgetElement = ref<HTMLElement | undefined>()
/**
* @note Do NOT convert style to a computed value, as it will cause lag when
* updating the style on different animation frames. Vue's computed value is
* evaluated asynchronously.
*/
const style = ref<CSSProperties>({})
const { style: positionStyle, updatePosition } = useAbsolutePosition({ const { style: positionStyle, updatePosition } = useAbsolutePosition({
useTransform: true useTransform: true
}) })
const { style: clippingStyle, updateClipPath } = useDomClipping() const { style: clippingStyle, updateClipPath } = useDomClipping()
const style = computed<CSSProperties>(() => ({
...positionStyle.value,
...(enableDomClipping.value ? clippingStyle.value : {}),
zIndex: widgetState.zIndex,
pointerEvents:
widgetState.readonly || widget.computedDisabled ? 'none' : 'auto',
opacity: widget.computedDisabled ? 0.5 : 1
}))
const canvasStore = useCanvasStore() const canvasStore = useCanvasStore()
const settingStore = useSettingStore() const settingStore = useSettingStore()
@@ -88,13 +86,28 @@ const updateDomClipping = () => {
) )
} }
/**
* @note mapping between canvas position and client position depends on the
* canvas element's position, so we need to watch the canvas element's position
* and update the position of the widget accordingly.
*/
const { left, top } = useElementBounding(canvasStore.getCanvas().canvas)
watch( watch(
() => widgetState, [() => widgetState, left, top],
(newState) => { ([widgetState, _, __]) => {
updatePosition(newState) updatePosition(widgetState)
if (enableDomClipping.value) { if (enableDomClipping.value) {
updateDomClipping() updateDomClipping()
} }
style.value = {
...positionStyle.value,
...(enableDomClipping.value ? clippingStyle.value : {}),
zIndex: widgetState.zIndex,
pointerEvents:
widgetState.readonly || widget.computedDisabled ? 'none' : 'auto',
opacity: widget.computedDisabled ? 0.5 : 1
}
}, },
{ deep: true } { deep: true }
) )

View File

@@ -1,5 +1,5 @@
import type { Size, Vector2 } from '@comfyorg/litegraph' import type { Size, Vector2 } from '@comfyorg/litegraph'
import { CSSProperties, computed, ref } from 'vue' import { CSSProperties, ref } from 'vue'
import { useCanvasPositionConversion } from '@/composables/element/useCanvasPositionConversion' import { useCanvasPositionConversion } from '@/composables/element/useCanvasPositionConversion'
import { useCanvasStore } from '@/stores/graphStore' import { useCanvasStore } from '@/stores/graphStore'
@@ -23,13 +23,20 @@ export function useAbsolutePosition(options: { useTransform?: boolean } = {}) {
lgCanvas lgCanvas
) )
const position = ref<PositionConfig>({ /**
pos: [0, 0], * @note Do NOT convert style to a computed value, as it will cause lag when
size: [0, 0] * updating the style on different animation frames. Vue's computed value is
}) * evaluated asynchronously.
*/
const style = ref<CSSProperties>({})
const style = computed<CSSProperties>(() => { /**
const { pos, size, scale = lgCanvas.ds.scale } = position.value * Compute the style of the element based on the position and size.
*
* @param position
*/
const computeStyle = (position: PositionConfig): CSSProperties => {
const { pos, size, scale = lgCanvas.ds.scale } = position
const [left, top] = canvasPosToClientPos(pos) const [left, top] = canvasPosToClientPos(pos)
const [width, height] = size const [width, height] = size
@@ -50,7 +57,7 @@ export function useAbsolutePosition(options: { useTransform?: boolean } = {}) {
width: `${width * scale}px`, width: `${width * scale}px`,
height: `${height * scale}px` height: `${height * scale}px`
} }
}) }
/** /**
* Update the position of the element on the litegraph canvas. * Update the position of the element on the litegraph canvas.
@@ -58,7 +65,7 @@ export function useAbsolutePosition(options: { useTransform?: boolean } = {}) {
* @param config * @param config
*/ */
const updatePosition = (config: PositionConfig) => { const updatePosition = (config: PositionConfig) => {
position.value = config style.value = computeStyle(config)
} }
return { return {