transition-mixins — cross-port transition class names
styles/theme/common/transition.scss is the single source of cross-port transition class names. It solves a real pain point:
Vue2’s transition class names are <name>-enter / <name>-leave (the starting state of enter / leave), while Vue3 and React changed them to <name>-enter-from / <name>-leave-from. Both ports express the same semantics (“enter start state” / “leave start state”) with different names. Previously 16 components hand-wrote duplicate selectors file by file, and fixing one place missed another.
The three mixins
Section titled “The three mixins”| Mixin | Parameters | Generated class names | Purpose |
|---|---|---|---|
lh-enter-both |
$selector (required), $child (optional) |
#{$selector}-enter, #{$selector}-enter-from |
Duplicate-writes the enter start state |
lh-leave-both |
$selector, $child? |
#{$selector}-leave, #{$selector}-leave-from |
Duplicate-writes the leave start state |
lh-transition-ends |
$selector, $child? |
#{$selector}-enter, #{$selector}-enter-from, #{$selector}-leave-to |
Transition start/end states (enter start duplicated + the leave end state, which is named identically on both ports) |
All three receive a style block through @content.
The $child parameter
Section titled “The $child parameter”Pass the second argument when the transition state needs to live on a descendant node; the generated selector then gets a #{$child} suffix:
// without $child: the state lives on the selector itself@include lh-enter-both('.lh-backtop-fade') { ... }// → .lh-backtop-fade-enter, .lh-backtop-fade-enter-from { ... }
// with $child: the state lives on the inner panel@include lh-enter-both('.lh-drawer-fade', '.lh-drawer__panel--right') { ... }// → .lh-drawer-fade-enter .lh-drawer__panel--right,// .lh-drawer-fade-enter-from .lh-drawer__panel--right { ... }Why these three
Section titled “Why these three”-enter-to/-leave-toare named identically on both ports and need no duplication.- Only the paired start states need duplicating:
-enter/-enter-fromand-leave/-leave-from. lh-transition-endscovers the common case where “the enter start and the leave end are the same thing” (a fade: opacity 0 → 1 → 0) — one mixin covers both the duplicated enter start state and the leave end state.
Usage examples
Section titled “Usage examples”An ordinary transition (backtop fade-in / fade-out)
Section titled “An ordinary transition (backtop fade-in / fade-out)”.lh-backtop-fade { @include lh-transition-ends('.lh-backtop-fade') { opacity: 0; }}Compile result:
.lh-backtop-fade-enter,.lh-backtop-fade-enter-from,.lh-backtop-fade-leave-to { opacity: 0;}Only the enter start state (dialog fade-in; the end state is not needed)
Section titled “Only the enter start state (dialog fade-in; the end state is not needed)”.lh-dialog-fade { @include lh-enter-both('.lh-dialog-fade') { opacity: 0; transform: scale(0.9); }}State on the inner panel (drawer slide-in)
Section titled “State on the inner panel (drawer slide-in)”.lh-drawer-fade { @include lh-enter-both('.lh-drawer-fade', '.lh-drawer__panel--right') { transform: translateX(100%); }}Vue3’s <transition name="lh-drawer-fade"> adds lh-drawer-fade-enter-from to the root element during the enter phase; the selector’s descendant matches the panel, so the panel translates — the root element itself does not move, avoiding a transform on the teleported root node interfering with positioning.
How to use it inside a component
Section titled “How to use it inside a component”@use 'theme/common/transition' as *;
.lh-drawer-fade { @include lh-enter-both('.lh-drawer-fade', '.lh-drawer__panel--right') { transform: translateX(100%); } @include lh-leave-both('.lh-drawer-fade', '.lh-drawer__panel--right') { transform: translateX(100%); }}UI layer (Vue3):
<transition name="lh-drawer-fade"> <div v-if="visible" class="lh-drawer">...</div></transition>UI layer (Vue2):
<transition name="lh-drawer-fade"> <div v-show="visible" class="lh-drawer">...</div></transition>Both ports use the same name, and the duplicated selectors generated by the mixins each match their own port’s class names. This is how “distributed injection, single-sourced content” lands for transition class names.
Caveats
Section titled “Caveats”- Component styles must never hand-write duplicate transition class names — always
@include(red line 2). verify:styles item 5 (transition class names single-sourced) scans for bare-enter-from/-leave-fromliterals. - The
@usepath:@use 'theme/common/transition' as *;(the relative path from each component’s scss, or the bare specifier at build time, is decided by the injection strategy). - The React case: the React build uses headless transitions (state-driven className), and the mixin-generated
-enter-fromclass names apply just the same. - The
$childsuffix is a descendant selector: it is not an&concatenation — the generated selector is.parent-enter .childand relies on the root element carrying the transition class name.
Related documentation
Section titled “Related documentation”- Style injection per port — the differences in style artifacts between the two ports
- Dark mode — tokens at runtime vs compile time
- Style source — the
styles/directory and BEM conventions
Copyright © 2024-2026 FengMo WenXue