Skip to content

Commit dc4dd59

Browse files
authored
fix(TransitionGroup): use offsetLeft and offsetTop instead of getBoundingClientRect to avoid transform scale affect animation (#6108)
close #6105
1 parent 40c4b2a commit dc4dd59

File tree

2 files changed

+71
-7
lines changed

2 files changed

+71
-7
lines changed

packages/runtime-dom/src/components/TransitionGroup.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,13 @@ import {
2929
} from '@vue/runtime-core'
3030
import { extend } from '@vue/shared'
3131

32-
const positionMap = new WeakMap<VNode, DOMRect>()
33-
const newPositionMap = new WeakMap<VNode, DOMRect>()
32+
interface Position {
33+
top: number
34+
left: number
35+
}
36+
37+
const positionMap = new WeakMap<VNode, Position>()
38+
const newPositionMap = new WeakMap<VNode, Position>()
3439
const moveCbKey = Symbol('_moveCb')
3540
const enterCbKey = Symbol('_enterCb')
3641

@@ -145,10 +150,10 @@ const TransitionGroupImpl: ComponentOptions = /*@__PURE__*/ decorate({
145150
instance,
146151
),
147152
)
148-
positionMap.set(
149-
child,
150-
(child.el as Element).getBoundingClientRect(),
151-
)
153+
positionMap.set(child, {
154+
left: (child.el as HTMLElement).offsetLeft,
155+
top: (child.el as HTMLElement).offsetTop,
156+
})
152157
}
153158
}
154159
}
@@ -189,7 +194,10 @@ function callPendingCbs(c: VNode) {
189194
}
190195

191196
function recordPosition(c: VNode) {
192-
newPositionMap.set(c, (c.el as Element).getBoundingClientRect())
197+
newPositionMap.set(c, {
198+
left: (c.el as HTMLElement).offsetLeft,
199+
top: (c.el as HTMLElement).offsetTop,
200+
})
193201
}
194202

195203
function applyTranslation(c: VNode): VNode | undefined {

packages/vue/__tests__/e2e/TransitionGroup.spec.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,62 @@ describe('e2e: TransitionGroup', () => {
646646
E2E_TIMEOUT,
647647
)
648648

649+
// #6105
650+
test(
651+
'with scale',
652+
async () => {
653+
await page().evaluate(() => {
654+
const { createApp, ref, onMounted } = (window as any).Vue
655+
createApp({
656+
template: `
657+
<div id="container">
658+
<div class="scale" style="transform: scale(2) translateX(50%) translateY(50%)">
659+
<transition-group tag="ul">
660+
<li v-for="item in items" :key="item">{{item}}</li>
661+
</transition-group>
662+
<button id="toggleBtn" @click="click">button</button>
663+
</div>
664+
</div>
665+
`,
666+
setup: () => {
667+
const items = ref(['a', 'b', 'c'])
668+
const click = () => {
669+
items.value.reverse()
670+
}
671+
672+
onMounted(() => {
673+
const styleNode = document.createElement('style')
674+
styleNode.innerHTML = `.v-move {
675+
transition: transform 0.5s ease;
676+
}`
677+
document.body.appendChild(styleNode)
678+
})
679+
680+
return { items, click }
681+
},
682+
}).mount('#app')
683+
})
684+
685+
const original_top = await page().$eval('ul li:nth-child(1)', node => {
686+
return node.getBoundingClientRect().top
687+
})
688+
const new_top = await page().evaluate(() => {
689+
const el = document.querySelector('ul li:nth-child(1)')
690+
const p = new Promise(resolve => {
691+
el!.addEventListener('transitionstart', () => {
692+
const new_top = el!.getBoundingClientRect().top
693+
resolve(new_top)
694+
})
695+
})
696+
;(document.querySelector('#toggleBtn') as any)!.click()
697+
return p
698+
})
699+
700+
expect(original_top).toBeLessThan(new_top as number)
701+
},
702+
E2E_TIMEOUT,
703+
)
704+
649705
test(
650706
'not leaking after children unmounted',
651707
async () => {

0 commit comments

Comments
 (0)