feat(pdf): restore smooth crossfade for Chrome 111+ using document.startViewTransition
Build and Deploy Verso / deploy (push) Successful in 11m24s
Build and Deploy Verso / deploy (push) Successful in 11m24s
The original code used container.startViewTransition(setDocument) with a synchronous callback, giving a 250ms CSS crossfade that looked smooth when the PDF happened to re-render before the animation ended — but was a race. Now there are three tiers: - Chrome 126+: element-level startViewTransition, async, waits for pagerendered - Chrome 111+ (Brave 138, Edge 111+): document-level startViewTransition with view-transition-name scoped to the PDF container, same async pattern - Firefox / Safari / older Chromium: canvas snapshot overlay (no animation, but seamless — introduced in build #108) The document-level path restores the smooth fade the user saw on Edge build #93, now guaranteed to crossfade old→new rather than old→blank. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -118,9 +118,25 @@ export default class PDFJSWrapper {
|
||||
})
|
||||
|
||||
const container = this.container as typeof this.container & {
|
||||
// supported since Chrome 126
|
||||
// element-level View Transitions (Level 2), Chrome 126+
|
||||
startViewTransition?: (cb: () => void | Promise<void>) => {
|
||||
ready: Promise<void>
|
||||
finished: Promise<void>
|
||||
}
|
||||
}
|
||||
const doc_ = document as typeof document & {
|
||||
// document-level View Transitions (Level 1), Chrome 111+
|
||||
startViewTransition?: (cb: () => void | Promise<void>) => {
|
||||
ready: Promise<void>
|
||||
finished: Promise<void>
|
||||
}
|
||||
}
|
||||
|
||||
// Shared error handler for transition.ready rejections.
|
||||
const onTransitionError = (err: any) => {
|
||||
// InvalidStateError just means the document was hidden during the transition.
|
||||
if (err?.name !== 'InvalidStateError') {
|
||||
captureException(err, { tags: { handler: 'pdf-preview' } })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,27 +144,33 @@ export default class PDFJSWrapper {
|
||||
typeof container.startViewTransition === 'function' &&
|
||||
document.visibilityState !== 'hidden'
|
||||
) {
|
||||
// Chrome 126+: element-level View Transition API.
|
||||
// The async callback holds the "before" snapshot until pagerendered fires,
|
||||
// so the crossfade goes old→new rather than old→blank.
|
||||
// Chrome 126+: element-level View Transition, scoped to this container.
|
||||
// Async callback holds the "before" snapshot until pagerendered fires.
|
||||
const transition = container.startViewTransition(async () => {
|
||||
setDocument()
|
||||
await firstPageRendered
|
||||
})
|
||||
|
||||
transition.ready.catch(err => {
|
||||
// ignore InvalidStateError, it just means the document was hidden
|
||||
if (err?.name !== 'InvalidStateError') {
|
||||
captureException(err, {
|
||||
tags: { handler: 'pdf-preview' },
|
||||
})
|
||||
}
|
||||
transition.ready.catch(onTransitionError)
|
||||
} else if (
|
||||
typeof doc_.startViewTransition === 'function' &&
|
||||
document.visibilityState !== 'hidden'
|
||||
) {
|
||||
// Chrome 111+ / Edge 111+: document-level View Transition.
|
||||
// Give the PDF container a unique view-transition-name so only it
|
||||
// crossfades; the rest of the page stays static.
|
||||
const name = 'ol-pdf-viewer'
|
||||
this.container.style.viewTransitionName = name
|
||||
const transition = doc_.startViewTransition(async () => {
|
||||
setDocument()
|
||||
await firstPageRendered
|
||||
})
|
||||
transition.finished.finally(() => {
|
||||
this.container.style.viewTransitionName = ''
|
||||
})
|
||||
transition.ready.catch(onTransitionError)
|
||||
} else {
|
||||
// All other browsers: snapshot the currently-rendered canvases into a
|
||||
// temporary overlay so setDocument()'s synchronous canvas-clear doesn't
|
||||
// produce a white flash. The overlay is removed once the first page of
|
||||
// the new document has been painted.
|
||||
// Firefox / Safari / very old Chromium: canvas snapshot overlay keeps
|
||||
// the old content visible while setDocument() clears and repaints.
|
||||
const snap = this._snapshotCanvases()
|
||||
setDocument()
|
||||
if (snap) {
|
||||
|
||||
Reference in New Issue
Block a user