yikart/AiToEarn · error · Error
Failed to create blob
Error message
Failed to create blob
What it means
VideoCoverSeting's handleConfirm throws this Error when the cropped cropper canvas's toBlob('image/jpeg', 0.92) resolves null, meaning the browser could not encode the cropped canvas to a JPEG blob. The catch block shows a generic uploadFailed toast.
Source
Thrown at project/aitoearn-web/src/components/PublishDialog/compoents/PubParmasTextarea/VideoCoverSeting.tsx:188
if (cropper.current) {
cropper.current.setAspectRatio(ratio ?? Number.NaN)
}
}, [])
/** 确认选择封面 */
const handleConfirm = useCallback(async () => {
if (!cropper.current || !imgFile)
return
setUploadLoading(true)
try {
const canvas = cropper.current.getCroppedCanvas()
const blob = await new Promise<Blob | null>((resolve) => {
canvas.toBlob(resolve, 'image/jpeg', 0.92)
})
if (!blob) {
throw new Error('Failed to create blob')
}
const cover = await formatImg({
blob,
path: `${saveImgId}.jpg`,
})
// 上传封面到 OSS
const uploadCoverRes = await uploadToOss(cover.file)
cover.ossUrl = getOssUrl(uploadCoverRes)
onChoosed(cover)
handleClose()
}
catch (error) {
console.error('上传封面失败:', error)
toast.error(t('videoCover.uploadFailed'))
}View on GitHub (pinned to d3aa8bea5b)
Solutions
- Load the cover image through the same-origin OSS proxy (getOssProxyPath) to prevent canvas tainting
- Guard that getCroppedCanvas() returns a canvas with width>0 && height>0 before toBlob
- Wait for image onload / cropper ready before enabling the confirm button
- Retry toBlob once; if still null, surface a specific crop error message
Example fix
// before
const canvas = cropper.current.getCroppedCanvas()
const blob = await new Promise<Blob | null>((resolve) => {
canvas.toBlob(resolve, 'image/jpeg', 0.92)
})
// after
const canvas = cropper.current.getCroppedCanvas()
if (!canvas || canvas.width === 0 || canvas.height === 0)
throw new Error('Cropped canvas is empty')
const blob = await new Promise<Blob | null>((resolve, reject) => {
canvas.toBlob((b) => b ? resolve(b) : reject(new Error('toBlob returned null')), 'image/jpeg', 0.92)
}) Defensive patterns
Strategy: validation
Validate before calling
const canvas = cropper.current.getCroppedCanvas()
if (!canvas || canvas.width === 0 || canvas.height === 0)
throw new Error('empty cropped canvas') Type guard
function isBlob(v: Blob | null | undefined): v is Blob {
return v instanceof Blob && v.size > 0
} Try / catch
try {
const blob = await new Promise<Blob | null>(r => canvas.toBlob(r, 'image/jpeg', 0.92))
if (!isBlob(blob)) throw new Error('Failed to create blob')
} catch (e) {
toast.error(t('videoCover.uploadFailed'))
} Prevention
- Load the cover image through the same-origin OSS proxy so the canvas is not tainted
- Only enable confirm after the image has loaded and the cropper is initialized
- Check cropped canvas dimensions before toBlob
- Retry once on null toBlob before failing
When it happens
Trigger: getCroppedCanvas() returned a canvas with zero width/height, or the canvas is tainted by cross-origin image data, causing canvas.toBlob to yield null.
Common situations: User confirms crop before the image fully loads; cropper container has 0 size; cover image loaded from a cross-origin OSS URL without the same-origin proxy (getOssProxyPath), tainting the canvas.
Related errors
AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31).
Data as JSON: /api/errors/de3a5d63c5227690.
Report an issue: GitHub.