返回 marp
how-to-make-custom-transition.md
根目录 / website / blog / how-to-make-custom-transition.md
1 ---
2 title: 'Marp CLI: How to make custom transition'
3 date: 2022-05-28
4 description: Marp CLI v2.4.0+ and Marp for VS Code v2.5.0+ have a stable support for page transitions with many useful built-in effects. But if you had not satisfied with any effects? Make your effects with CSS!
5 author: Yuki Hattori
6 github: yhatt
7 image: /og-images/how-to-make-custom-transition.jpg
8 ---
9
10 [readme]: https://github.com/marp-team/marp-cli/blob/main/docs/bespoke-transitions/README.md
11 [built-in]: https://github.com/marp-team/marp-cli/blob/main/docs/bespoke-transitions/README.md#built-in-transitions
12 [view transitions api]: https://www.w3.org/TR/css-view-transitions-1/
13
14 **[Marp CLI v2](/blog/202205-ecosystem-update#marp-cli-v2)** has supported [brand-new page transitions for the `bespoke` HTML template](/blog/202205-ecosystem-update#slide-transition-experiment). You can use this stable transition support in either Marp CLI v2.4.0+ or Marp for VS Code v2.5.0+.
15
16 Effective transitions will help make a dramatic presentation. Adding a touch of effects to slides is often common in great talks. By viewing HTML slide in the browser that supports [View Transitions API] (Chrome 110+), or Marp CLI with `--preview` option, you can start to use [varied 33 transition effects][built-in] out of the box, by [just a simple definition `transition` directive](https://github.com/marp-team/marp-cli/blob/main/docs/bespoke-transitions/README.md#transition-local-directive).
17
18 Built-in transitions should be useful for 90% of Marp users. But what you can do if there are no effects you are satisfied with? Make your effects in CSS! Marp can register your custom animation set declared in CSS as a named transition, and use it in the Markdown slide.
19
20 <!-- more -->
21
22 ### Index
23
24 This article will describe the following things:
25
26 1. **[The anatomy of a transition](#the-anatomy-of-a-transition)**: How the transition effect will work in Marp
27 1. **[Declare custom transitions](#declare-custom-transitions)**: How to register custom transitions by CSS
28 1. **[Helpful tips for making your transition](#tips)**
29
30 [See also the official documentation about transitions in Marp CLI.][readme]
31
32 _If using [built-in transitions made by us][built-in] was enough, you don't need to read this article._ Please save your time, with keeping enjoying our transitions in your Markdown slide! :)
33
34 > In this article, the word "transition" is meaning the slide transition effect in Marp. Please note that it is not meaning [`transition` property in CSS](https://developer.mozilla.org/docs/Web/CSS/CSS_Transitions/Using_CSS_transitions).
35
36 # The anatomy of a transition
37
38 The first what the custom transition author has to know is "How the page transition effect is realized in a presentation slide".
39
40 Let's consider what is happening when the slide page was navigated from 1 to 2. If no transitions were set to the slide, the first page will just disappear, and appear on the second page immediately. If it has a transition effect, a certain time for playing animations will insert between switching pages.
41
42 ![The anatomy of a transition](/assets/how-to-make-custom-transition/transition-diagram.jpg 'The anatomy of a transition')
43
44 An important thing during transition is that 2 slides are presented in the view at the same time like layers. All kinds of effects produce smooth transitions by applying specific animations to one or both slides.
45
46 In Marp, the slide page that was shown before transition calls as **"Outgoing slide"**, and the next page to appear after transition calls as **"Incoming slide"**. Slide pages may have an inverse relationship when brought the backward navigation, but the meaning of "incoming" and "outgoing" is always consistent.
47
48 If you could figure them out, you probably also grasp that you have to respect the following 2 principles:
49
50 - **The outgoing slide** should have **an animation to hide** the slide.
51 - **The incoming slide** should have **an animation to show** the slide.
52
53 If either or both was not respected in a transition effect, it would become a weird transition.
54
55 > Marp CLI's `bespoke` template will make two slide layers when navigated, and apply suitable animation keyframes declared in CSS.
56
57 # Declare custom transitions
58
59 ## Simple keyframe declaration
60
61 Let's get started with a simple keyframe declaration for [the dissolve effect (also known as the cross-fade effect)](<https://en.wikipedia.org/wiki/Dissolve_(filmmaking)>), to learn how to set custom transition animation. Marp uses [standard syntax for CSS animation `@keyframes`](https://developer.mozilla.org/docs/Web/CSS/@keyframes) to declare transitions.
62
63 When applying the dissolve effect to transition principles, you can derive that the effect needs these animations:
64
65 - The outgoing slide has an animation to **decrease opacity from 100% to 0%**.
66 - The incoming slide has an animation to **increase opacity from 0% to 100%**.
67
68 There are opposite changes with each other. In this case, you can define animations for both slide layers by one `@keyframes` declaration.
69
70 First, declare `@keyframes` at-rule with the conventional name specified by Marp in your Markdown.
71
72 ```markdown
73 ---
74 transition: dissolve
75 style: |
76 @keyframes marp-transition-dissolve {
77 /* ... */
78 }
79 ---
80
81 # Slide 1
82
83 ---
84
85 <!-- _class: invert -->
86
87 # Slide 2
88 ```
89
90 **`marp-transition-xxxxxxxx`** is the rule of animation name to register the transition with a simple declaration. For using declared transition in Marp slide, assign `transition` local directive with the name declared in `xxxxxxxx`.
91
92 > This example is using [`style` global directive](https://marpit.marp.app/directives?id=tweak-theme-style) to declare keyframes. Of course, you also can use [the inline `<style>` element](https://marpit.marp.app/theme-css?id=tweak-style-through-markdown) or [custom theme CSS](https://marpit.marp.app/theme-css) to declare.
93
94 Well, declare animation details at keyframes. In a simple declaration, you only have to set animation for the outgoing slide. For the incoming slide, Marp will set the animation in the reverse direction automatically.
95
96 ```css
97 @keyframes marp-transition-dissolve {
98 from {
99 opacity: 1;
100 }
101 to {
102 opacity: 0;
103 }
104 }
105 ```
106
107 > This example has been declared `from` keyframe for clarity, but you can omit it because `opacity: 1` is a default style.
108
109 Did you want more? That's it! Try to test this transition in the HTML slide with the browser that supports [View Transitions API], or [a preview window in Marp CLI](https://github.com/marp-team/marp-cli#preview-window---preview---p).
110
111 ```bash
112 npx @marp-team/marp-cli@^2.4.0 --preview ./transition.md
113 ```
114
115 You have made the first custom transition!
116
117 ![autoplay The dissolve effect with timeline diagram](/assets/how-to-make-custom-transition/dissolve-opacity.mp4)
118
119 In this article, the example is simplified for teaching how to make a custom transition, and there is a bit of difference from the built-in transition `fade` for getting the same effect. `dissolve` effect is looking good, but there is [a general pitfall about cross fading](https://jakearchibald.com/2021/dom-cross-fade/).
120
121 ## Split animations into outgoing and incoming
122
123 A simple declaration should work in some transition types well, but it's not that all transitions have exactly contrary animations to each other. In reality, different animations for the outgoing slide and incoming slide are required in most cases.
124
125 For example, the slide up effect must have these animations:
126
127 - The outgoing slide should **move from the viewport to the upper outer**.
128 - The incoming slide should **move from the lower outer to the viewport**.
129
130 So you can declare split animations for each layer rather than declaring a single animation. Set `@keyframes` with the prefix of the target transition: **`marp-outgoing-transition-xxxxxxxx`** and **`marp-incoming-transition-xxxxxxxx`**.
131
132 ```markdown
133 ---
134 transition: slide-up
135 style: |
136 @keyframes marp-outgoing-transition-slide-up {
137 from { transform: translateY(0%); }
138 to { transform: translateY(-100%); }
139 }
140 @keyframes marp-incoming-transition-slide-up {
141 from { transform: translateY(100%); }
142 to { transform: translateY(0%); }
143 }
144 ---
145
146 # Slide 1
147
148 ---
149
150 <!-- _class: invert -->
151
152 # Slide 2
153 ```
154
155 Unlike the simple transition, there is no auto-reversed animation in the incoming slide. Each animation should define in the right direction.
156
157 ![The timeline diagram of slide-up transition](/assets/how-to-make-custom-transition/slide-up-translate-y.png 'The timeline diagram of slide-up transition')
158
159 ## Transition for backward navigation
160
161 If you have tested the above slide-up transition example, you may have noticed that is having a move to up also when slide navigation going to back has occurred.
162
163 ![Wrong direction in slide up transition](/assets/how-to-make-custom-transition/slide-up-wrong-direction.gif ' ')
164
165 It brings a wrong user interaction and is not intuitive. You should want to provide the animation for the correct direction when occurred backward navigation.
166
167 We are providing several solutions to deal with this.
168
169 ### `--marp-transition-direction` CSS variable
170
171 While playing transition, `--marp-transition-direction` [CSS custom property (as known as CSS variables)](https://developer.mozilla.org/docs/Web/CSS/Using_CSS_custom_properties) will be available in `@keyframes`.
172
173 It provides `1` in forwarding navigation, or `-1` in backward navigation. Using [`var(--marp-transition-direction)`](https://developer.mozilla.org/docs/Web/CSS/var) together with [`calc()`](https://developer.mozilla.org/docs/Web/CSS/calc) function would be useful to calculate the position in response to the direction of slide navigation.
174
175 <!-- prettier-ignore-start -->
176
177 ```css
178 @keyframes marp-outgoing-transition-slide-up {
179 from { transform: translateY(0%); }
180 to { transform: translateY(calc(var(--marp-transition-direction, 1) * -100%)); }
181 }
182 @keyframes marp-incoming-transition-slide-up {
183 from { transform: translateY(calc(var(--marp-transition-direction, 1) * 100%)); }
184 to { transform: translateY(0%); }
185 }
186 ```
187
188 <!-- prettier-ignore-end -->
189
190 And now, the slide-up custom transition is working completely in both directional navigation!
191
192 ![Slide up transition with correct directions](/assets/how-to-make-custom-transition/slide-up-correct-direction.gif ' ')
193
194 > NOTE: Any other CSS variables defined in the context of animation keyframes cannot use in keyframes.
195
196 ### Set custom animations for backward transition
197
198 Alternatively, you also can set more animation keyframes that are specific for backward navigation.
199
200 Declare `@keyframes` with the **`backward-` prefix to the custom transition name**, just like as **`marp-transition-backward-xxxxxxxx`**. It is available in both simple keyframes declaration and split keyframes declaration.
201
202 <!-- prettier-ignore-start -->
203
204 ```css
205 @keyframes marp-incoming-transition-triangle {
206 /* Wipe effect from left top */
207 from { clip-path: polygon(0% 0%, 0% 0%, 0% 0%); }
208 to { clip-path: polygon(0% 0%, 200% 0%, 0% 200%); }
209 }
210
211 @keyframes marp-incoming-transition-backward-triangle {
212 /* Wipe effect from right bottom */
213 from { clip-path: polygon(100% 100%, 100% 100%, 100% 100%); }
214 to { clip-path: polygon(-100% 100%, 100% -100%, 100% 100%); }
215 }
216 ```
217
218 <!-- prettier-ignore-end -->
219
220 In backward navigation, each layer will try to use the backward keyframes first, and fall back to the normal keyframes if not declared. To disable unintended fallback in backward animations, set an empty declaration of `@keyframes`.
221
222 <!-- prettier-ignore-start -->
223
224 ```css
225 @keyframes marp-outgoing-transition-zoom-out {
226 from { transform: scale(1); }
227 to { transform: scale(0); }
228 }
229 @keyframes marp-incoming-transition-zoom-out {
230 /* Send the incoming slide layer to back */
231 from { z-index: -1; }
232 to { z-index: -1; }
233 }
234
235 /* ⬇️ Declare empty keyframes to disable fallback ⬇️ */
236 @keyframes marp-outgoing-transition-backward-zoom-out {}
237 @keyframes marp-incoming-transition-backward-zoom-out {
238 from { transform: scale(0); }
239 to { transform: scale(1); }
240 }
241 ```
242
243 <!-- prettier-ignore-end -->
244
245 OK, I've described all about declarations for the custom transition!
246
247 # Tips
248
249 ## Easing function
250
251 Each transition has a linear easing by default. You can specify [`animation-timing-function` property within individual keyframes](https://developer.mozilla.org/en-US/docs/Web/CSS/animation-timing-function#:~:text=A%20keyframe%27s%20timing%20function%20is%20applied%20on%20a%20property%2Dby%2Dproperty%20basis%20from%20the%20keyframe%20on%20which%20it%20is%20specified%20until%20the%20next%20keyframe%20specifying%20that%20property%2C%20or%20until%20the%20end%20of%20the%20animation%20if%20there%20is%20no%20subsequent%20keyframe%20specifying%20that%20property) if you want.
252
253 > Setting [`animation-timing-function: step-end;`](https://developer.mozilla.org/docs/Web/CSS/animation-timing-function#step-end) to a keyframe can make paused animation until the next keyframe.
254
255 ## Duration
256
257 We have a fixed duration time of `0.5s` as default for every transition. If you want to set a different default duration for your custom transition, please set `--marp-transition-duration` property in the first keyframe (`from` or `0%`).
258
259 <!-- prettier-ignore-start -->
260
261 ```css
262 @keyframes marp-incoming-transition-gate {
263 from {
264 /* Set the default duration of the "gate" transition as 1 second. */
265 --marp-transition-duration: 1s;
266
267 clip-path: inset(0 50%);
268 }
269 to { clip-path: inset(0); }
270 }
271
272 @keyframes marp-outgoing-transition-backward-gate {
273 from {
274 /* You also can set a different default for backward transition as necessary. */
275 /* --marp-transition-duration: 1.5s; */
276
277 clip-path: inset(0);
278 }
279 to { clip-path: inset(0 50%); }
280 }
281 @keyframes marp-incoming-transition-backward-gate {
282 from { z-index: -1; }
283 to { z-index: -1; }
284 }
285 ```
286
287 <!-- prettier-ignore-end -->
288
289 The slide author can override the default duration at any time, through the `transition` local directive in Markdown (`<!-- transition: fade 2s -->`).
290
291 ## Fixed property
292
293 If some of the properties required a fixed value while playing transition, try to set the same declaration into `from` (0%) and `to` (100%).
294
295 <!-- prettier-ignore-start -->
296
297 ```css
298 @keyframes marp-outgoing-transition-pin {
299 /* Use fixed transform-origin */
300 from {
301 transform-origin: top left;
302 animation-timing-function: ease-in;
303 }
304 to {
305 transform-origin: top left;
306 transform: rotate(90deg);
307 }
308 }
309
310 @keyframes marp-incoming-transition-pin {
311 /* Send the incoming slide layer to back */
312 from { z-index: -1; }
313 to { z-index: -1; }
314 }
315 ```
316
317 <!-- prettier-ignore-end -->
318
319 ## Layer order
320
321 [As presented in a diagram earlier](#the-anatomy-of-a-transition), the incoming slide layer always will be stacked on the top of the outgoing slide layer. According to the kind of transition, this order may be not suitable.
322
323 A fixed property [`z-index: -1`](https://developer.mozilla.org/docs/Web/CSS/z-index) is helpful to send the incoming slide layer to back.
324
325 > A fixed `z-index: 1` to the outgoing slide (send to front) is also getting the same result, but currently setting a positive number to `z-index` may bring animation jank in Chrome.
326
327 ## Change layer order during a transition
328
329 If you want to swap the order of layers during animation, try to animate `z-index` property.
330
331 <!-- prettier-ignore-start -->
332
333 ```css
334 @keyframes marp-incoming-transition-swap {
335 /* Incoming slide will swap from `back` to `front` at 50% of animation */
336 from { z-index: -1; }
337 to { z-index: 0; }
338
339 /* Declarations for moving animation */
340 0% { transform: translateX(0); }
341 50% { transform: translateX(50%); }
342 100% { transform: translateX(0); }
343 }
344
345 @keyframes marp-outgoing-transition-swap {
346 0% { transform: translateX(0); }
347 50% { transform: translateX(-50%); }
348 100% { transform: translateX(0); }
349 }
350 ```
351
352 <!-- prettier-ignore-end -->
353
354 `z-index` is always taking an integer value, and interpolated `z-index` value by animation does not take any decimal points too. So animating from `z-index: -1` to `z-index: 0` is exactly meaning to set `-1` at the first half of duration and `0` at the last half, except if using a non-linear easing function.
355
356 ## Frequently used properties in transition
357
358 [There are a lot of animatable CSS properties](https://developer.mozilla.org/docs/Web/CSS/CSS_animated_properties), and the following properties are frequently animated in built-in transitions.
359
360 - [`opacity`](https://developer.mozilla.org/docs/Web/CSS/opacity)
361 - [`transform`](https://developer.mozilla.org/docs/Web/CSS/transform)
362 - [`filter`](https://developer.mozilla.org/docs/Web/CSS/filter)
363 - [`clip-path`](https://developer.mozilla.org/docs/Web/CSS/clip-path)
364 - [`mask-image`](https://developer.mozilla.org/docs/Web/CSS/mask-image) (`-webkit-mask-image`)
365 - [`box-shadow`](https://developer.mozilla.org/docs/Web/CSS/box-shadow)
366 - [`z-index`](https://developer.mozilla.org/docs/Web/CSS/z-index)
367
368 # Try it!
369
370 Transitions for Marp CLI's bespoke template backed by [View Transitions API] in the browser, provides flexibility to design your talk as you like. Custom transition brings out your boundless creativity, without complex JS codings, just declarative definitions in CSS.
371
372 We are really looking forward to what creative transition effects our community will create!
373
374 Share the custom transition you've made with [Marp community](https://github.com/orgs/marp-team/discussions). You can provide custom theme CSS including a bunch of custom transitions too.
375
375 lines MARKDOWN