Skip to content
Open
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
7 changes: 7 additions & 0 deletions src/browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const isSafari = () => {
const platform = navigator.platform.toLowerCase()
if (platform.includes('linux') || platform.includes('win')) {
return false
}
return /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
}
11 changes: 9 additions & 2 deletions src/embed-images.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Options } from './types'
import { embedResources } from './embed-resources'
import { toArray, isInstanceOfElement } from './util'
import { toArray, isInstanceOfElement, setReCanvasDrawCount } from './util'
import { isDataUrl, resourceToDataURL } from './dataurl'
import { getMimeType } from './mimes'

Expand Down Expand Up @@ -67,7 +67,14 @@ async function embedImageNode<T extends HTMLElement | SVGImageElement>(

const image = clonedNode as HTMLImageElement
if (image.decode) {
image.decode = resolve as any
image.onload = () => {
image.decode().finally(() => {
requestAnimationFrame(resolve)
// fix safari blank image bug
setReCanvasDrawCount(options)
})
}
image.decoding = 'sync'
}

if (image.loading === 'lazy') {
Expand Down
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
canvasToBlob,
nodeToDataURL,
checkCanvasDimensions,
reCanvasDraw,
} from './util'

export async function toSvg<T extends HTMLElement>(
Expand Down Expand Up @@ -54,6 +55,8 @@ export async function toCanvas<T extends HTMLElement>(
}

context.drawImage(img, 0, 0, canvas.width, canvas.height)
// fix safari blank image bug
await reCanvasDraw(options, img, canvas, context)

return canvas
}
Expand Down
4 changes: 4 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,7 @@ export interface Options {
*/
onImageErrorHandler?: OnErrorEventHandler
}

export interface PrivateOptions extends Options {
reCanvasDrawCount?: number
}
36 changes: 33 additions & 3 deletions src/util.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Options } from './types'
import { isSafari } from './browser'
import type { Options, PrivateOptions } from './types'

export function resolveUrl(url: string, baseUrl: string | null): string {
// url is absolute already
Expand Down Expand Up @@ -200,13 +201,13 @@ export function createImage(url: string): Promise<HTMLImageElement> {
return new Promise((resolve, reject) => {
const img = new Image()
img.onload = () => {
img.decode().then(() => {
img.decode().finally(() => {
requestAnimationFrame(() => resolve(img))
})
}
img.onerror = reject
img.crossOrigin = 'anonymous'
img.decoding = 'async'
img.decoding = 'sync'
img.src = url
})
}
Expand Down Expand Up @@ -259,3 +260,32 @@ export const isInstanceOfElement = <
isInstanceOfElement(nodePrototype, instance)
)
}

export const setReCanvasDrawCount = (option: PrivateOptions) => {
if (!isSafari()) return
option.reCanvasDrawCount ??= 0
option.reCanvasDrawCount += 1
}

export const reCanvasDraw = async (
option: PrivateOptions,
img: HTMLImageElement,
canvas: HTMLCanvasElement,
ctx: CanvasRenderingContext2D,
) => {
if (!isSafari()) return

for (let i = 0; i < (option.reCanvasDrawCount ?? 0); i++) {
// eslint-disable-next-line no-await-in-loop
await new Promise<void>((resolve) => {
setTimeout(() => {
// safari preloading
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
resolve()
}, 100)
})
}

Reflect.deleteProperty(option, 'reCanvasDrawCount')
}