diff --git a/CHANGELOG.md b/CHANGELOG.md index 670ecd9a..6135364c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ - Use arm64 hosted runner to build container image for ARM64 ([#635](https://github.com/marp-team/marp-cli/issues/635), [#637](https://github.com/marp-team/marp-cli/pull/637)) +### Fixed + +- Improve bespoke transition animations for Safari compatibility ([#572](https://github.com/marp-team/marp-cli/issues/572), [#641](https://github.com/marp-team/marp-cli/pull/641)) + ## v4.1.1 - 2025-01-19 ### Fixed diff --git a/docs/bespoke-transitions/README.md b/docs/bespoke-transitions/README.md index 58fc02a9..9fe7d2e6 100644 --- a/docs/bespoke-transitions/README.md +++ b/docs/bespoke-transitions/README.md @@ -20,7 +20,7 @@ To show transition animations, a viewer has to show HTML slide in the browser wh - **Chrome**: ✅ (111-) - **Edge**: ✅ (111-) -- **Safari**: ✅ (18.2- / [#572](https://github.com/marp-team/marp-cli/issues/572)) +- **Safari**: ✅ (18.2-) - **Firefox**: :x: ## `transition` local directive @@ -231,7 +231,8 @@ Marp CLI has provided useful [33 built-in transitions](../../src/engine/transiti -> **Note** +> [!NOTE] +> > If the viewer has enabled the reduce motion feature on their device, the transition animation will be forced to a simple `fade` animation regardless of the specified transition. See also "[Reduce transitions by a viewer](#reduce-transitions-by-a-viewer)". ### Duration @@ -252,6 +253,8 @@ For making custom transitions, all you have to know is only about CSS. Define an Marp prefers the custom transition if defined the transition with same name as built-in transitions. +> [!TIP] +> > See also our blog article: **"[Marp CLI: How to make custom transition](https://marp.app/blog/how-to-make-custom-transition)"** ### Simple keyframe declaration @@ -487,7 +490,7 @@ If you want to swap the order of layers during animation, try to animate `z-inde } ``` -> `z-index` is always taking an integer value, and interpolated `z-index` value by animation does not take any decimal points too. +`z-index` is always taking an integer value, and interpolated `z-index` value by animation does not take any decimal points too. ## Morphing animations @@ -505,7 +508,11 @@ The slide author can visualize the relationship between the different elements i If there were multiple pairs defined by `view-transition-name` CSS property with different names, each elements will morph at the same time. Elements that were not marked by `view-transition-name` still follow the selected animation by `transition` local directive. -> **Warning** Each morphable elements marked by `view-transition-name` must have uniquely named in a slide page. If there were multiple elements named by `view-transition-name` with the same name in a single page, View Transition API does not apply _the whole of transition animation_. +> [!WARNING] +> +> Each morphable elements marked by `view-transition-name` must have uniquely named in a slide page. If there were multiple elements named by `view-transition-name` with the same name in a single page, View Transition API does not apply _the whole of transition animation_. +> +> The morphing animation has been confirmed working correctly on Chrome and Edge. Safari also supports this kind of animation, but the correct animation is not granted, due to the effect of [WebKit's a long-standing bug](https://bugs.webkit.org/show_bug.cgi?id=23113) (and [marpit-svg-polyfill](https://github.com/marp-team/marpit-svg-polyfill) to patch that). ### Example diff --git a/docs/bespoke-transitions/images/flip.gif b/docs/bespoke-transitions/images/flip.gif index 38133324..a5011f0c 100644 Binary files a/docs/bespoke-transitions/images/flip.gif and b/docs/bespoke-transitions/images/flip.gif differ diff --git a/docs/bespoke-transitions/images/rotate.gif b/docs/bespoke-transitions/images/rotate.gif index cc159a1a..f0dd0b11 100644 Binary files a/docs/bespoke-transitions/images/rotate.gif and b/docs/bespoke-transitions/images/rotate.gif differ diff --git a/src/engine/transition/keyframes/cover.scss b/src/engine/transition/keyframes/cover.scss index 6a0b6f6d..b5be42fc 100644 --- a/src/engine/transition/keyframes/cover.scss +++ b/src/engine/transition/keyframes/cover.scss @@ -6,11 +6,16 @@ transform: translateX(10%); animation-timing-function: ease-out; } + + to { + transform: translateX(0%); + } } @keyframes marp-outgoing-transition-backward-__builtin__cover { from { animation-timing-function: ease-out; + transform: translateX(0%); } to { diff --git a/src/engine/transition/keyframes/drop.scss b/src/engine/transition/keyframes/drop.scss index ae032ac3..8e899289 100644 --- a/src/engine/transition/keyframes/drop.scss +++ b/src/engine/transition/keyframes/drop.scss @@ -3,6 +3,14 @@ rgba(0, 0, 0, 30%) 0 3vh 5vh, rgba(0, 0, 0, 22%) 0 2vh 1.5vh; } +// [Safari CSS hack] +// In Safari, applying shadow causes a rendering issue +_::-webkit-full-page-media, +_:future, +:root { + --bespoke-marp-transition-drop-shadow: none; +} + @keyframes marp-incoming-transition-__builtin__drop { 0% { transform: translateY(-100%); @@ -55,6 +63,7 @@ @keyframes marp-outgoing-transition-backward-__builtin__drop { 0% { z-index: 1; + transform: translateY(0); animation-timing-function: ease-in; box-shadow: var(--bespoke-marp-transition-drop-shadow); } diff --git a/src/engine/transition/keyframes/flip.scss b/src/engine/transition/keyframes/flip.scss index cef70104..830aad1c 100644 --- a/src/engine/transition/keyframes/flip.scss +++ b/src/engine/transition/keyframes/flip.scss @@ -4,9 +4,9 @@ } 50% { - transform: perspective(800px) translateZ(-400px) + transform: perspective(75vw) rotateY(calc(var(--marp-transition-direction, 1) * -90deg)); - opacity: 0.5; + opacity: 0.75; animation-timing-function: step-start; } @@ -22,9 +22,9 @@ } 50% { - transform: perspective(800px) translateZ(-400px) + transform: perspective(75vw) rotateY(calc(var(--marp-transition-direction, 1) * 90deg)); - opacity: 0.5; + opacity: 0.75; animation-timing-function: ease-out; } } diff --git a/src/engine/transition/keyframes/pivot.scss b/src/engine/transition/keyframes/pivot.scss index 0bf8848b..87d78663 100644 --- a/src/engine/transition/keyframes/pivot.scss +++ b/src/engine/transition/keyframes/pivot.scss @@ -19,6 +19,7 @@ @keyframes marp-outgoing-transition-backward-__builtin__pivot { from { transform-origin: left top; + transform: rotate(0deg); animation-timing-function: ease-in; } diff --git a/src/engine/transition/keyframes/pull.scss b/src/engine/transition/keyframes/pull.scss index 48daf2f5..8f65128c 100644 --- a/src/engine/transition/keyframes/pull.scss +++ b/src/engine/transition/keyframes/pull.scss @@ -3,6 +3,7 @@ @keyframes marp-outgoing-transition-__builtin__pull { from { animation-timing-function: ease-out; + transform: translateX(0); } to { @@ -19,6 +20,10 @@ animation-timing-function: ease-out; transform: translateX(-100%); } + + to { + transform: translateX(0); + } } @keyframes marp-outgoing-transition-backward-__builtin__pull { diff --git a/src/engine/transition/keyframes/push.scss b/src/engine/transition/keyframes/push.scss index c988131e..461984eb 100644 --- a/src/engine/transition/keyframes/push.scss +++ b/src/engine/transition/keyframes/push.scss @@ -5,11 +5,16 @@ animation-timing-function: ease-out; transform: translateX(100%); } + + to { + transform: translateX(0); + } } @keyframes marp-outgoing-transition-backward-__builtin__push { from { animation-timing-function: ease-out; + transform: translateX(0); } to { diff --git a/src/engine/transition/keyframes/reveal.scss b/src/engine/transition/keyframes/reveal.scss index 3ec67fb5..24b11346 100644 --- a/src/engine/transition/keyframes/reveal.scss +++ b/src/engine/transition/keyframes/reveal.scss @@ -3,6 +3,7 @@ @keyframes marp-outgoing-transition-__builtin__reveal { from { animation-timing-function: ease-out; + transform: translateX(0%); } to { @@ -24,4 +25,8 @@ transform: translateX(-10%); animation-timing-function: ease-out; } + + to { + transform: translateX(0%); + } } diff --git a/src/engine/transition/keyframes/rotate.scss b/src/engine/transition/keyframes/rotate.scss index a020f630..2aa51c12 100644 --- a/src/engine/transition/keyframes/rotate.scss +++ b/src/engine/transition/keyframes/rotate.scss @@ -1,16 +1,18 @@ @keyframes marp-outgoing-transition-__builtin__rotate { from { z-index: 1; - transform: perspective(100vw) translateZ(-40vw) rotateY(0deg) - translateZ(40vw); + + // NOTE: 0.0001deg is a workaround for a bug in Safari + transform: perspective(100vw) translateZ(-50vw) rotateY(0.0001deg) + translateZ(50vw); animation-timing-function: ease-in-out; } to { z-index: 0; - transform: perspective(100vw) translateZ(-40vw) + transform: perspective(100vw) translateZ(-50vw) rotateY(calc(var(--marp-transition-direction, 1) * -180deg)) - translateZ(40vw) + translateZ(50vw) rotateY(calc(var(--marp-transition-direction, 1) * 180deg)); opacity: 0; } @@ -20,16 +22,16 @@ from { opacity: 0; z-index: 0; - transform: perspective(100vw) translateZ(-40vw) + transform: perspective(100vw) translateZ(-50vw) rotateY(calc(var(--marp-transition-direction, 1) * 180deg)) - translateZ(40vw) + translateZ(50vw) rotateY(calc(var(--marp-transition-direction, 1) * -180deg)); animation-timing-function: ease-in-out; } to { z-index: 1; - transform: perspective(100vw) translateZ(-40vw) rotateY(0deg) - translateZ(40vw); + transform: perspective(100vw) translateZ(-50vw) rotateY(0.0001deg) + translateZ(50vw); } } diff --git a/src/engine/transition/keyframes/slide.scss b/src/engine/transition/keyframes/slide.scss index c2f6a50d..234f6b09 100644 --- a/src/engine/transition/keyframes/slide.scss +++ b/src/engine/transition/keyframes/slide.scss @@ -3,19 +3,15 @@ transform: translateX(calc(var(--marp-transition-direction, 1) * 100vw)); animation-timing-function: ease-in-out; } - - to { - animation-timing-function: ease-in-out; - } } @keyframes marp-outgoing-transition-__builtin__slide { from { + transform: translateX(0); animation-timing-function: ease-in-out; } to { transform: translateX(calc(var(--marp-transition-direction, 1) * -100vw)); - animation-timing-function: ease-in-out; } } diff --git a/src/engine/transition/keyframes/swap.scss b/src/engine/transition/keyframes/swap.scss index fa20eaf7..0f6b50dc 100644 --- a/src/engine/transition/keyframes/swap.scss +++ b/src/engine/transition/keyframes/swap.scss @@ -1,6 +1,7 @@ @keyframes marp-outgoing-transition-__builtin__swap { 0% { z-index: 1; + transform: none; transform-origin: calc(var(--marp-transition-direction, 1) * 50% + 50%) calc(var(--marp-transition-direction, 1) * 50% + 50%); animation-timing-function: ease-in-out; @@ -13,6 +14,7 @@ } 100% { + transform: none; transform-origin: calc(var(--marp-transition-direction, 1) * 50% + 50%) calc(var(--marp-transition-direction, 1) * 50% + 50%); filter: brightness(0.5); @@ -23,6 +25,7 @@ @keyframes marp-incoming-transition-__builtin__swap { 0% { filter: brightness(0.75); + transform: none; transform-origin: calc(var(--marp-transition-direction, 1) * -50% + 50%) calc(var(--marp-transition-direction, 1) * -50% + 50%); animation-timing-function: ease-in-out; @@ -35,6 +38,7 @@ } 100% { + transform: none; transform-origin: calc(var(--marp-transition-direction, 1) * -50% + 50%) calc(var(--marp-transition-direction, 1) * -50% + 50%); } diff --git a/src/engine/transition/keyframes/swipe.scss b/src/engine/transition/keyframes/swipe.scss index 04ada39a..0ca6e650 100644 --- a/src/engine/transition/keyframes/swipe.scss +++ b/src/engine/transition/keyframes/swipe.scss @@ -1,13 +1,26 @@ +:root { + --bespoke-marp-transition-swipe-shadow: 6px 6px 10px 6px rgba(0, 0, 0, 25%); +} + +// [Safari CSS hack] +// In Safari, applying shadow causes a rendering issue +_::-webkit-full-page-media, +_:future, +:root { + --bespoke-marp-transition-swipe-shadow: none; +} + @keyframes marp-outgoing-transition-__builtin__swipe { 0% { animation-timing-function: ease-in; - box-shadow: 6px 6px 10px 6px rgba(0, 0, 0, 25%); + box-shadow: var(--bespoke-marp-transition-swipe-shadow); + transform: none; transform-origin: 100% 100%; z-index: 1; } 100% { - box-shadow: 6px 6px 10px 6px rgba(0, 0, 0, 25%); + box-shadow: var(--bespoke-marp-transition-swipe-shadow); transform: translate(calc(-100vw - 30px), -30vh) rotate(-30deg); transform-origin: 100% 100%; z-index: 1; @@ -29,6 +42,7 @@ @keyframes marp-outgoing-transition-backward-__builtin__swipe { 0% { + transform: none; animation-timing-function: ease-in-out; } @@ -41,14 +55,15 @@ @keyframes marp-incoming-transition-backward-__builtin__swipe { 0% { animation-timing-function: ease-out; - box-shadow: 6px 6px 10px 6px rgba(0, 0, 0, 25%); + box-shadow: var(--bespoke-marp-transition-swipe-shadow); transform: translate(calc(-100vw - 30px), 30vh) rotate(30deg); transform-origin: 100% 0%; z-index: 1; } 100% { - box-shadow: 6px 6px 10px 6px rgba(0, 0, 0, 25%); + box-shadow: var(--bespoke-marp-transition-swipe-shadow); + transform: none; transform-origin: 100% 0%; z-index: 1; } diff --git a/src/engine/transition/keyframes/zoom.scss b/src/engine/transition/keyframes/zoom.scss index dbfb5bb8..f8f176f2 100644 --- a/src/engine/transition/keyframes/zoom.scss +++ b/src/engine/transition/keyframes/zoom.scss @@ -14,6 +14,7 @@ @keyframes marp-outgoing-transition-backward-__builtin__zoom { from { + transform: none; animation-timing-function: ease-in; }