Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Experimental transition] Adopt the latest spec of View Transition API #489

Merged
merged 7 commits into from
Jan 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .stylelintrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ rules:
property-no-unknown:
- true
- ignoreProperties:
- page-transition-tag
- view-transition-name
selector-pseudo-element-no-unknown:
- true
- ignorePseudoElements:
- /^page-transition-/
- /^view-transition-/
selector-type-no-unknown:
- true
- ignoreTypes:
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## [Unreleased]

### Breaking

- [Experimental transition] Adopt the latest spec of [View Transitions](https://www.w3.org/TR/css-view-transitions-1/) that is available in Chrome 109 and later ([#447](https://github.com/marp-team/marp-cli/issues/447), [#483](https://github.com/marp-team/marp-cli/issues/483), [#489](https://github.com/marp-team/marp-cli/pull/489))

### Changed

- Upgrade Node.js and dependent packages ([#493](https://github.com/marp-team/marp-cli/pull/493))
Expand Down
8 changes: 5 additions & 3 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,13 @@ export class MarpCLIConfig {

if (transition) {
info(
'An EXPERIMENTAL transition support for bespoke template is enabled. ' +
'It is using the shared element transition API proposal and it is not yet stable. ' +
'An EXPERIMENTAL transition support for bespoke template has been enabled. ' +
`It is powered by View Transitions API proposal that is ${chalk.yellow`currently available only in Chrome 109 and later.`} ` +
(preview
? ''
: `Recommend to use with ${chalk.yellow`--preview`} option for trying transitions. `) +
: `Recommend to use with ${chalk.yellow`--preview`} option for trying transitions surely. `) +
`And please don't forget ${chalk.bold
.redBright`you're using not yet stable feature`}. ` +
`Track the latest information at ${chalk.blueBright`https://github.com/marp-team/marp-cli/issues/447`}.`
)
}
Expand Down
26 changes: 13 additions & 13 deletions src/templates/bespoke/_transition.scss
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,16 @@

@mixin transition {
@at-root {
::page-transition-container(*) {
::view-transition-group(*) {
animation-duration: var(
--marp-bespoke-transition-animation-duration,
0.5s
);
animation-timing-function: ease;
}

::page-transition-outgoing-image(*),
::page-transition-incoming-image(*) {
::view-transition-old(*),
::view-transition-new(*) {
animation-name: var(
--marp-bespoke-transition-animation-name,
var(
Expand All @@ -53,36 +53,36 @@
mix-blend-mode: normal;
}

::page-transition-outgoing-image(*) {
::view-transition-old(*) {
--marp-bespoke-transition-animation-name-fallback: __bespoke_marp_transition_reduced_outgoing__;

animation-timing-function: ease;
}

::page-transition-incoming-image(*) {
::view-transition-new(*) {
--marp-bespoke-transition-animation-name-fallback: __bespoke_marp_transition_reduced_incoming__;

animation-timing-function: ease;
}

::page-transition-outgoing-image(root),
::page-transition-incoming-image(root) {
::view-transition-old(root),
::view-transition-new(root) {
animation-timing-function: linear;
}

::page-transition-outgoing-image(__bespoke_marp_transition_osc__),
::page-transition-incoming-image(__bespoke_marp_transition_osc__) {
::view-transition-old(__bespoke_marp_transition_osc__),
::view-transition-new(__bespoke_marp_transition_osc__) {
animation-name: __bespoke_marp_transition_osc__ !important; /* no animation */
animation-duration: 0s !important;
}

::page-transition-incoming-image(__bespoke_marp_transition_osc__) {
::view-transition-new(__bespoke_marp_transition_osc__) {
opacity: 0 !important;
}

.bespoke-marp-transition-warming-up::page-transition-container(*),
.bespoke-marp-transition-warming-up::page-transition-incoming-image(*),
.bespoke-marp-transition-warming-up::page-transition-outgoing-image(*) {
.bespoke-marp-transition-warming-up::view-transition-group(*),
.bespoke-marp-transition-warming-up::view-transition-new(*),
.bespoke-marp-transition-warming-up::view-transition-old(*) {
animation-play-state: paused !important;
}
}
Expand Down
10 changes: 5 additions & 5 deletions src/templates/bespoke/bespoke.scss
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ $progress-height: 5px;

&:not(.bespoke-marp-active) {
* {
/* Unset page-transition-tag in inactive pages because it must be unique for a document */
page-transition-tag: none !important;
/* Unset view-transition-name in inactive pages because it must be unique for a document */
view-transition-name: none !important;
}
}

Expand Down Expand Up @@ -69,7 +69,7 @@ $progress-height: 5px;
@media (prefers-reduced-motion: reduce) {
* {
/* Disable shared element transition declared by users */
page-transition-tag: none !important;
view-transition-name: none !important;
}
}
}
Expand Down Expand Up @@ -112,8 +112,8 @@ $progress-height: 5px;
/* Hack for Chrome to show OSC overlay onto video correctly */
will-change: transform;

/* Page transition API */
page-transition-tag: __bespoke_marp_transition_osc__;
/* View Transition */
view-transition-name: __bespoke_marp_transition_osc__;

> * {
margin-left: 6px;
Expand Down
103 changes: 50 additions & 53 deletions src/templates/bespoke/transition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ interface TransitionCallbackOption {
cond: (e: any) => boolean
}

interface DocumentTransition {
start(callback: () => void): Promise<void>
setElement(element: Element, tag: string, options?: any): void
abandon(): void
interface ViewTransition {
domUpdated: Promise<void>
finished: Promise<void>
ready: Promise<void>
skipTransition: () => void
}

export const transitionStyleId = '_tSId' as const
Expand Down Expand Up @@ -46,12 +47,11 @@ const reducedKeyframes: MarpTransitionKeyframes = {
}

const bespokeTransition = (deck) => {
const createDocumentTransition: any = document['createDocumentTransition']
if (!createDocumentTransition) return
if (!document['startViewTransition']) return

const transitionDuringState = (
value?: boolean | DocumentTransition
): boolean | DocumentTransition | undefined => {
value?: boolean | ViewTransition
): boolean | ViewTransition | undefined => {
if (value !== undefined) deck[transitionDuring] = value
return deck[transitionDuring]
}
Expand All @@ -60,23 +60,6 @@ const bespokeTransition = (deck) => {

transitionDuringState(false)

const doTransition = (
transition: DocumentTransition | true, // true means no transition
callback: () => void | Promise<void>
) => {
requestAnimationFrame(async () => {
transitionDuringState(transition)

try {
await callback()
} catch (e) {
console.warn(e)
} finally {
transitionDuringState(false)
}
})
}

// Prefetch using keyframes
prepareMarpTransitions(
...Array.from(
Expand Down Expand Up @@ -115,7 +98,7 @@ const bespokeTransition = (deck) => {
if (currentTransition) {
if (e[transitionApply]) return true
if (typeof currentTransition === 'object') {
currentTransition.abandon()
currentTransition.skipTransition()
if (e.forSync) return true
}
return false
Expand Down Expand Up @@ -143,14 +126,25 @@ const bespokeTransition = (deck) => {
getMarpTransitionKeyframes(transitionData.name, {
builtinFallback: transitionData.builtinFallback,
}).then((keyframes) => {
if (!keyframes) return doTransition(true, () => fn(e))
if (!keyframes) {
// Fallback to default navigation
transitionDuringState(true) // true means no transition

try {
fn(e)
} finally {
transitionDuringState(false)
}

return
}

// Normalize keyframes for preferred reduced motion
let normalizedKeyframes = keyframes

if (prefersReducedMotion.matches) {
console.warn(
'Use a constant animation to transition because preferring reduced motion by viewer has detected. '
'Use a constant animation to transition because preferring reduced motion by viewer has detected.'
)
normalizedKeyframes = reducedKeyframes
}
Expand All @@ -170,38 +164,41 @@ const bespokeTransition = (deck) => {
duration: transitionData.duration,
}).forEach((styleText) => style.sheet?.insertRule(styleText))

try {
// Start transition
const transition: DocumentTransition =
document['createDocumentTransition']()
// Start transition
const rootClassList = document.documentElement.classList
rootClassList.add(transitionWarmUpClass)

const rootClassList = document.documentElement.classList
rootClassList.add(transitionWarmUpClass)
let navigated = false

let navigated = false
const navigate = () => {
if (navigated) return

const navigate = () => {
if (navigated) return
fn(e)
navigated = true

fn(e)
navigated = true
rootClassList.remove(transitionWarmUpClass)
}

rootClassList.remove(transitionWarmUpClass)
}
const finalize = () => {
transitionDuringState(false)

style.remove()
rootClassList.remove(transitionWarmUpClass)
}

doTransition(transition, async () => {
try {
await transition.start(navigate)
} catch (err) {
console.error(err)
navigate()
} finally {
style.remove()
rootClassList.remove(transitionWarmUpClass)
}
})
try {
transitionDuringState(true) // to prevent unexpected navigation

const transition: ViewTransition =
document['startViewTransition'](navigate)

transitionDuringState(transition)
transition.finished.finally(finalize)
} catch (e) {
transitionDuringState(false)
console.error(e)

navigate()
finalize()
}
})

Expand Down
6 changes: 3 additions & 3 deletions src/templates/bespoke/utils/transition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ export const resolveAnimationStyles = (
resolveDefaultDuration(opts.backward ? 'backward' : 'forward')

if (duration !== undefined) {
rules.push(`::page-transition-container(*){${durationCSSVar}:${duration};}`)
rules.push(`::view-transition-group(*){${durationCSSVar}:${duration};}`)
}

const getStyleMap = (vars: ReturnType<typeof resolveAnimationVariables>) =>
Expand All @@ -252,10 +252,10 @@ export const resolveAnimationStyles = (
.join('')

rules.push(
`::page-transition-outgoing-image(root){${getStyleMap(
`::view-transition-old(root){${getStyleMap(
resolveAnimationVariables(keyframes, { ...opts, type: 'outgoing' })
)}}`,
`::page-transition-incoming-image(root){${getStyleMap(
`::view-transition-new(root){${getStyleMap(
resolveAnimationVariables(keyframes, { ...opts, type: 'incoming' })
)}}`
)
Expand Down
4 changes: 2 additions & 2 deletions src/utils/puppeteer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ export const generatePuppeteerLaunchArgs = async () => {
if (isDocker() || process.env.CI)
args.add('--disable-features=VizDisplayCompositor')

// Enable DocumentTransition API
if (!process.env.CI) args.add('--enable-blink-features=DocumentTransition')
// Enable View transition API
if (!process.env.CI) args.add('--enable-blink-features=ViewTransition')

// LayoutNG Printing
if (process.env.CHROME_LAYOUTNG_PRINTING)
Expand Down
13 changes: 0 additions & 13 deletions test/_browser/documentTransition.ts

This file was deleted.

31 changes: 31 additions & 0 deletions test/_browser/viewTransition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
beforeEach(() => {
Object.defineProperty(document, 'startViewTransition', {
writable: true,
configurable: true,
value: jest.fn((callback: () => void) => {
const domUpdated = new Promise<void>((resolve) => {
callback()
resolve()
})

return Object.create(ViewTransition, {
domUpdated: {
enumerable: true,
value: domUpdated,
},
ready: {
enumerable: true,
value: Promise.resolve(),
},
finished: {
enumerable: true,
value: domUpdated,
},
})
}),
})
})

export const skipTransition = jest.fn()

export const ViewTransition = Object.seal({ skipTransition })
2 changes: 1 addition & 1 deletion test/marp-cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,7 @@ describe('Marp CLI', () => {
})

const matcher = expect.stringContaining(
'transition support for bespoke template is enabled'
'transition support for bespoke template has been enabled'
)
const previewRecommendation = expect.stringContaining('--preview')

Expand Down
Loading