diff --git a/README.md b/README.md index 1b1e0a8b..0d5629a9 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,9 @@ All the top level functions accept DOM node and rendering options, and return a - [toSvg](#toSvg) - [toJpeg](#toJpeg) - [toBlob](#toBlob) +- [toImage](#toImage) - [toCanvas](#toCanvas) +- [toCanvasList](#toCanvasList) - [toPixelData](#toPixelData) Go with the following examples. @@ -114,6 +116,16 @@ htmlToImage.toBlob(document.getElementById('my-node')) }); ``` +#### toImage +Get a HTMLImageElement, which is a svg image that you can scale it to a big size and it will not blurred. + +```js +htmlToImage.toImage(document.getElementById('my-node')) + .then(function (img) { + document.body.appendChild(img); + }); +``` + #### toCanvas Get a HTMLCanvasElement, and display it right away: @@ -124,6 +136,17 @@ htmlToImage.toCanvas(document.getElementById('my-node')) }); ``` +#### toCanvasList +Get a array of HTMLCanvasElement. Not like `toCanvas` which is limited by [canvas size](https://jhildenbiddle.github.io/canvas-size/#/?id=test-results), + `toCanvasList` can get rid of the limitation of canvas size, so this can export a very large html: + +```js +htmlToImage.toCanvasList(document.getElementById('my-node')) + .then(function (canvasList) { + canvasList.map(canvas => document.body.appendChild(canvas)); + }); +``` + #### toPixelData Get the raw pixel data as a [Uint8Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) with every 4 array elements representing the RGBA data of a pixel: diff --git a/src/index.ts b/src/index.ts index 0c5c9bfa..c5b87ca8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,6 +10,7 @@ import { canvasToBlob, nodeToDataURL, checkCanvasDimensions, + getDimensionLimit, } from './util' export async function toSvg( @@ -25,14 +26,20 @@ export async function toSvg( return datauri } +export async function toImage( + node: T, + options: Options = {}, +): Promise { + const svg = await toSvg(node, options) + return createImage(svg) +} + export async function toCanvas( node: T, options: Options = {}, ): Promise { + const img = await toImage(node, options) const { width, height } = getImageSize(node, options) - const svg = await toSvg(node, options) - const img = await createImage(svg) - const canvas = document.createElement('canvas') const context = canvas.getContext('2d')! const ratio = options.pixelRatio || getPixelRatio() @@ -58,6 +65,49 @@ export async function toCanvas( return canvas } +export async function toCanvasList( + node: T, + options: Options = {}, +): Promise> { + const img = await toImage(node, options) + const { width, height } = getImageSize(node, options) + const ratio = options.pixelRatio || getPixelRatio() + let canvasWidth = (options.canvasWidth || width) * ratio + let canvasHeight = (options.canvasHeight || height) * ratio + const dimensionLimit = getDimensionLimit() + if (canvasWidth > dimensionLimit) { + canvasHeight *= dimensionLimit / canvasWidth + canvasWidth = dimensionLimit + } + + const result: Array = [] + const scale = canvasWidth / img.width + for (let curY = 0; curY < canvasHeight; curY += dimensionLimit) { + const canvas = document.createElement('canvas') + const context = canvas.getContext('2d')! + const height1 = Math.min(canvasHeight - curY, dimensionLimit) + canvas.width = canvasWidth + canvas.height = height1 + if (options.backgroundColor) { + context.fillStyle = options.backgroundColor + context.fillRect(0, 0, canvas.width, canvas.height) + } + context.drawImage( + img, + 0, + curY / scale, + canvasWidth / scale, + height1 / scale, + 0, + 0, + canvasWidth, + height1, + ) + result.push(canvas) + } + return result +} + export async function toPixelData( node: T, options: Options = {}, diff --git a/src/util.ts b/src/util.ts index 61fbebcd..37ca7f20 100644 --- a/src/util.ts +++ b/src/util.ts @@ -129,19 +129,62 @@ export function checkCanvasDimensions(canvas: HTMLCanvasElement) { canvas.height *= canvasDimensionLimit / canvas.width canvas.width = canvasDimensionLimit } else { - canvas.width *= canvasDimensionLimit / canvas.height - canvas.height = canvasDimensionLimit + const height1 = getMaxCanvasHeight(canvas.width) + canvas.width *= height1 / canvas.height + canvas.height = height1 } } else if (canvas.width > canvasDimensionLimit) { canvas.height *= canvasDimensionLimit / canvas.width canvas.width = canvasDimensionLimit } else { - canvas.width *= canvasDimensionLimit / canvas.height - canvas.height = canvasDimensionLimit + const height = getMaxCanvasHeight(canvas.width) + canvas.width *= height / canvas.height + canvas.height = height } } } +export function getDimensionLimit(): number { + return canvasDimensionLimit +} + +const dimenstionLimitCache: { [width: number]: number } = {} + +export function getMaxCanvasHeight(width: number): number { + let val = dimenstionLimitCache[width] + if (val) return val + val = test() + dimenstionLimitCache[width] = val + return val + + function test(): number { + const heights = [ + // Chrome 83 (Mac, Win) + 65535, + // Chrome 70 (Mac, Win) + // Chrome 68 (Android 4.4-9) + // Firefox 63 (Mac, Win) + 32767, + // Edge 17 (Win) + // IE11 (Win) + // 16384, + ] + for (let i = 0; i < heights.length; i++) { + try { + const canvas = document.createElement('canvas') + canvas.width = width + canvas.height = heights[i] + const ctx = canvas.getContext('2d')! + ctx.drawImage(new Image(), 0, 0) // check + return heights[i] + } catch (e) { + // ignore + } + } + return canvasDimensionLimit + } +} + export function canvasToBlob( canvas: HTMLCanvasElement, options: Options = {}, @@ -210,7 +253,7 @@ export async function nodeToDataURL( const foreignObject = document.createElementNS(xmlns, 'foreignObject') // fix: if ratio=2 and style.border='1px', in html it is actually rendered to 1px, but in it is rendered to 2px. Then height is different and the bottom 1px is lost, 10 nodes will lost 10px. - var ratio = getPixelRatio(); + const ratio = getPixelRatio() svg.setAttribute('width', `${width / ratio}`) svg.setAttribute('height', `${height / ratio}`) svg.setAttribute('viewBox', `0 0 ${width} ${height}`) @@ -252,21 +295,22 @@ export const isInstanceOfElement = < export function getStyles() { const styles = document.querySelectorAll('style,link[rel="stylesheet"]') - const ps: Array> = [] + const promises: Array> = [] toArray(styles).forEach((el) => { const e = el as Element if (e.tagName === 'LINK') { const href = e.getAttribute('href') - if (href) ps.push(getCssText(href).catch(() => '')) + if (href) + promises.push( + fetch(href) + .then((r) => r.text()) + .catch(() => ''), + ) } else { - ps.push(Promise.resolve(e.innerHTML)) + promises.push(Promise.resolve(e.innerHTML)) } }) - return Promise.all(ps).then((arr) => { + return Promise.all(promises).then((arr) => { return arr.join('\n\n') }) - - function getCssText(url: string) { - return fetch(url).then((r) => r.text()) - } }