fix: using existing canvas option (#2017)

* refactor: document cleanup to DocumentCloner

* fix: using existing canvas option

* fix: lint errors

* fix: preview transform origin
This commit is contained in:
Niklas von Hertzen 2019-09-25 23:34:18 -07:00 committed by GitHub
parent 34b06d6365
commit 076492042a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 147 additions and 14 deletions

View File

@ -42,7 +42,7 @@
ctx.stroke(); ctx.stroke();
document.querySelector("button").addEventListener("click", function() { document.querySelector("button").addEventListener("click", function() {
html2canvas(document.querySelector("#content"), {canvas: canvas}).then(function(canvas) { html2canvas(document.querySelector("#content"), {canvas: canvas, scale: 1}).then(function(canvas) {
console.log('Drew on the existing canvas'); console.log('Drew on the existing canvas');
}); });
}, false); }, false);

91
src/__tests__/index.ts Normal file
View File

@ -0,0 +1,91 @@
import html2canvas from '../index';
import {CanvasRenderer} from '../render/canvas/canvas-renderer';
import {DocumentCloner} from '../dom/document-cloner';
import {COLORS} from '../css/types/color';
jest.mock('../core/logger');
jest.mock('../css/layout/bounds');
jest.mock('../dom/document-cloner');
jest.mock('../dom/node-parser', () => {
return {
isBodyElement: () => false,
isHTMLElement: () => false,
parseTree: jest.fn().mockImplementation(() => {
return {styles: {}};
})
};
});
jest.mock('../render/stacking-context');
jest.mock('../render/canvas/canvas-renderer');
describe('html2canvas', () => {
// eslint-disable-next-line @typescript-eslint/no-object-literal-type-assertion
const element = {
ownerDocument: {
defaultView: {
pageXOffset: 12,
pageYOffset: 34
}
}
} as HTMLElement;
it('should render with an element', async () => {
DocumentCloner.destroy = jest.fn().mockReturnValue(true);
await html2canvas(element);
expect(CanvasRenderer).toHaveBeenLastCalledWith(
expect.objectContaining({
backgroundColor: 0xffffffff,
scale: 1,
height: 50,
width: 200,
x: 0,
y: 0,
scrollX: 12,
scrollY: 34,
canvas: undefined
})
);
expect(DocumentCloner.destroy).toBeCalled();
});
it('should have transparent background with backgroundColor: null', async () => {
await html2canvas(element, {backgroundColor: null});
expect(CanvasRenderer).toHaveBeenLastCalledWith(
expect.objectContaining({
backgroundColor: COLORS.TRANSPARENT
})
);
});
it('should use existing canvas when given as option', async () => {
// eslint-disable-next-line @typescript-eslint/no-object-literal-type-assertion
const canvas = {} as HTMLCanvasElement;
await html2canvas(element, {canvas});
expect(CanvasRenderer).toHaveBeenLastCalledWith(
expect.objectContaining({
canvas
})
);
});
it('should not remove cloned window when removeContainer: false', async () => {
DocumentCloner.destroy = jest.fn();
await html2canvas(element, {removeContainer: false});
expect(CanvasRenderer).toHaveBeenLastCalledWith(
expect.objectContaining({
backgroundColor: 0xffffffff,
scale: 1,
height: 50,
width: 200,
x: 0,
y: 0,
scrollX: 12,
scrollY: 34,
canvas: undefined
})
);
expect(DocumentCloner.destroy).not.toBeCalled();
});
});

View File

@ -0,0 +1,17 @@
export class Logger {
debug() {}
static create() {}
static destroy() {}
static getInstance(): Logger {
return logger;
}
info() {}
error() {}
}
const logger = new Logger();

View File

@ -0,0 +1,4 @@
export const {Bounds} = jest.requireActual('../bounds');
export const parseBounds = () => {
return new Bounds(0, 0, 200, 50);
};

View File

@ -0,0 +1,16 @@
export class DocumentCloner {
clonedReferenceElement?: HTMLElement;
constructor() {
// eslint-disable-next-line @typescript-eslint/no-object-literal-type-assertion
this.clonedReferenceElement = {} as HTMLElement;
}
toIFrame() {
return Promise.resolve({});
}
static destroy() {
return true;
}
}

View File

@ -410,6 +410,14 @@ export class DocumentCloner {
: ` ${PSEUDO_HIDE_ELEMENT_CLASS_AFTER}`; : ` ${PSEUDO_HIDE_ELEMENT_CLASS_AFTER}`;
return anonymousReplacedElement; return anonymousReplacedElement;
} }
static destroy(container: HTMLIFrameElement): boolean {
if (container.parentNode) {
container.parentNode.removeChild(container);
return true;
}
return false;
}
} }
enum PseudoElementType { enum PseudoElementType {

View File

@ -116,6 +116,7 @@ const renderElement = async (element: HTMLElement, opts: Partial<Options>): Prom
const renderOptions = { const renderOptions = {
id: instanceName, id: instanceName,
cache: options.cache, cache: options.cache,
canvas: options.canvas,
backgroundColor, backgroundColor,
scale: options.scale, scale: options.scale,
x: options.x, x: options.x,
@ -153,7 +154,7 @@ const renderElement = async (element: HTMLElement, opts: Partial<Options>): Prom
} }
if (options.removeContainer === true) { if (options.removeContainer === true) {
if (!cleanContainer(container)) { if (!DocumentCloner.destroy(container)) {
Logger.getInstance(instanceName).error(`Cannot detach cloned iframe as it is not in the DOM anymore`); Logger.getInstance(instanceName).error(`Cannot detach cloned iframe as it is not in the DOM anymore`);
} }
} }
@ -163,11 +164,3 @@ const renderElement = async (element: HTMLElement, opts: Partial<Options>): Prom
CacheStorage.destroy(instanceName); CacheStorage.destroy(instanceName);
return canvas; return canvas;
}; };
const cleanContainer = (container: HTMLIFrameElement): boolean => {
if (container.parentNode) {
container.parentNode.removeChild(container);
return true;
}
return false;
};

View File

@ -71,10 +71,12 @@ export class CanvasRenderer {
this.canvas = options.canvas ? options.canvas : document.createElement('canvas'); this.canvas = options.canvas ? options.canvas : document.createElement('canvas');
this.ctx = this.canvas.getContext('2d') as CanvasRenderingContext2D; this.ctx = this.canvas.getContext('2d') as CanvasRenderingContext2D;
this.options = options; this.options = options;
if (!options.canvas) {
this.canvas.width = Math.floor(options.width * options.scale); this.canvas.width = Math.floor(options.width * options.scale);
this.canvas.height = Math.floor(options.height * options.scale); this.canvas.height = Math.floor(options.height * options.scale);
this.canvas.style.width = `${options.width}px`; this.canvas.style.width = `${options.width}px`;
this.canvas.style.height = `${options.height}px`; this.canvas.style.height = `${options.height}px`;
}
this.fontMetrics = new FontMetrics(document); this.fontMetrics = new FontMetrics(document);
this.ctx.scale(this.options.scale, this.options.scale); this.ctx.scale(this.options.scale, this.options.scale);
this.ctx.translate(-options.x + options.scrollX, -options.y + options.scrollY); this.ctx.translate(-options.x + options.scrollX, -options.y + options.scrollY);

View File

@ -42,8 +42,10 @@ function onBrowserChange(browserTest: Test) {
previewImage.src = `/results/${browserTest.screenshot}.png`; previewImage.src = `/results/${browserTest.screenshot}.png`;
if (browserTest.devicePixelRatio > 1) { if (browserTest.devicePixelRatio > 1) {
previewImage.style.transform = `scale(${1 / browserTest.devicePixelRatio})`; previewImage.style.transform = `scale(${1 / browserTest.devicePixelRatio})`;
previewImage.style.transformOrigin = 'top left';
} else { } else {
previewImage.style.transform = ''; previewImage.style.transform = '';
previewImage.style.transformOrigin = '';
} }
} }
} }