fix(TransitionGroup): use offsetLeft and offsetTop instead of getBoundingClientRect to avoid transform scale affect animation (#6108)
ci / test (push) Waiting to run Details
ci / continuous-release (push) Waiting to run Details
size data / upload (push) Waiting to run Details

close #6105
This commit is contained in:
Jooies 2025-11-05 17:20:25 +08:00 committed by GitHub
parent 40c4b2a876
commit dc4dd594fb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 71 additions and 7 deletions

View File

@ -29,8 +29,13 @@ import {
} from '@vue/runtime-core'
import { extend } from '@vue/shared'
const positionMap = new WeakMap<VNode, DOMRect>()
const newPositionMap = new WeakMap<VNode, DOMRect>()
interface Position {
top: number
left: number
}
const positionMap = new WeakMap<VNode, Position>()
const newPositionMap = new WeakMap<VNode, Position>()
const moveCbKey = Symbol('_moveCb')
const enterCbKey = Symbol('_enterCb')
@ -145,10 +150,10 @@ const TransitionGroupImpl: ComponentOptions = /*@__PURE__*/ decorate({
instance,
),
)
positionMap.set(
child,
(child.el as Element).getBoundingClientRect(),
)
positionMap.set(child, {
left: (child.el as HTMLElement).offsetLeft,
top: (child.el as HTMLElement).offsetTop,
})
}
}
}
@ -189,7 +194,10 @@ function callPendingCbs(c: VNode) {
}
function recordPosition(c: VNode) {
newPositionMap.set(c, (c.el as Element).getBoundingClientRect())
newPositionMap.set(c, {
left: (c.el as HTMLElement).offsetLeft,
top: (c.el as HTMLElement).offsetTop,
})
}
function applyTranslation(c: VNode): VNode | undefined {

View File

@ -646,6 +646,62 @@ describe('e2e: TransitionGroup', () => {
E2E_TIMEOUT,
)
// #6105
test(
'with scale',
async () => {
await page().evaluate(() => {
const { createApp, ref, onMounted } = (window as any).Vue
createApp({
template: `
<div id="container">
<div class="scale" style="transform: scale(2) translateX(50%) translateY(50%)">
<transition-group tag="ul">
<li v-for="item in items" :key="item">{{item}}</li>
</transition-group>
<button id="toggleBtn" @click="click">button</button>
</div>
</div>
`,
setup: () => {
const items = ref(['a', 'b', 'c'])
const click = () => {
items.value.reverse()
}
onMounted(() => {
const styleNode = document.createElement('style')
styleNode.innerHTML = `.v-move {
transition: transform 0.5s ease;
}`
document.body.appendChild(styleNode)
})
return { items, click }
},
}).mount('#app')
})
const original_top = await page().$eval('ul li:nth-child(1)', node => {
return node.getBoundingClientRect().top
})
const new_top = await page().evaluate(() => {
const el = document.querySelector('ul li:nth-child(1)')
const p = new Promise(resolve => {
el!.addEventListener('transitionstart', () => {
const new_top = el!.getBoundingClientRect().top
resolve(new_top)
})
})
;(document.querySelector('#toggleBtn') as any)!.click()
return p
})
expect(original_top).toBeLessThan(new_top as number)
},
E2E_TIMEOUT,
)
test(
'not leaking after children unmounted',
async () => {