Implementing cropping and dimension options for rendering (Fix #1230)

This commit is contained in:
Niklas von Hertzen 2017-09-27 22:14:50 +08:00
parent ae47d901a1
commit 53dd885279
16 changed files with 254 additions and 61 deletions

View File

@ -4,6 +4,7 @@
* Complete rewrite of library * Complete rewrite of library
##### Breaking Changes ##### ##### Breaking Changes #####
* Remove deprecated onrendered callback, calling `html2canvas` returns a `Promise<HTMLCanvasElement>` * Remove deprecated onrendered callback, calling `html2canvas` returns a `Promise<HTMLCanvasElement>`
* Removed option `type`, same results can be achieved by assigning `x`, `y`, `scrollX`, `scrollY`, `width` and `height` properties.
##### New featues / fixes ##### ##### New featues / fixes #####
* Add support for scaling canvas (defaults to device pixel ratio) * Add support for scaling canvas (defaults to device pixel ratio)

View File

@ -41,13 +41,22 @@ export class Bounds {
this.height = h; this.height = h;
} }
static fromClientRect(clientRect: ClientRect): Bounds { static fromClientRect(clientRect: ClientRect, scrollX: number, scrollY: number): Bounds {
return new Bounds(clientRect.left, clientRect.top, clientRect.width, clientRect.height); return new Bounds(
clientRect.left + scrollX,
clientRect.top + scrollY,
clientRect.width,
clientRect.height
);
} }
} }
export const parseBounds = (node: HTMLElement | SVGSVGElement): Bounds => { export const parseBounds = (
return Bounds.fromClientRect(node.getBoundingClientRect()); node: HTMLElement | SVGSVGElement,
scrollX: number,
scrollY: number
): Bounds => {
return Bounds.fromClientRect(node.getBoundingClientRect(), scrollX, scrollY);
}; };
export const calculatePaddingBox = (bounds: Bounds, borders: Array<Border>): Bounds => { export const calculatePaddingBox = (bounds: Bounds, borders: Array<Border>): Bounds => {

View File

@ -109,6 +109,8 @@ export class DocumentCloner {
const iframeKey = generateIframeKey(); const iframeKey = generateIframeKey();
tempIframe.setAttribute('data-html2canvas-internal-iframe-key', iframeKey); tempIframe.setAttribute('data-html2canvas-internal-iframe-key', iframeKey);
const {width, height} = parseBounds(node, 0, 0);
this.imageLoader.cache[iframeKey] = getIframeDocumentElement(node, this.options) this.imageLoader.cache[iframeKey] = getIframeDocumentElement(node, this.options)
.then(documentElement => { .then(documentElement => {
return this.renderer( return this.renderer(
@ -123,11 +125,14 @@ export class DocumentCloner {
removeContainer: this.options.removeContainer, removeContainer: this.options.removeContainer,
scale: this.options.scale, scale: this.options.scale,
target: new CanvasRenderer(), target: new CanvasRenderer(),
type: 'view', width,
height,
x: 0,
y: 0,
windowWidth: documentElement.ownerDocument.defaultView.innerWidth, windowWidth: documentElement.ownerDocument.defaultView.innerWidth,
windowHeight: documentElement.ownerDocument.defaultView.innerHeight, windowHeight: documentElement.ownerDocument.defaultView.innerHeight,
offsetX: documentElement.ownerDocument.defaultView.pageXOffset, scrollX: documentElement.ownerDocument.defaultView.pageXOffset,
offsetY: documentElement.ownerDocument.defaultView.pageYOffset scrollY: documentElement.ownerDocument.defaultView.pageYOffset
}, },
this.logger.child(iframeKey) this.logger.child(iframeKey)
); );
@ -338,7 +343,7 @@ const getIframeDocumentElement = (
.then(html => .then(html =>
createIframeContainer( createIframeContainer(
node.ownerDocument, node.ownerDocument,
parseBounds(node) parseBounds(node, 0, 0)
).then(cloneIframeContainer => { ).then(cloneIframeContainer => {
const cloneWindow = cloneIframeContainer.contentWindow; const cloneWindow = cloneIframeContainer.contentWindow;
const documentClone = cloneWindow.document; const documentClone = cloneWindow.document;
@ -411,6 +416,8 @@ export const cloneWindow = (
renderer: (element: HTMLElement, options: Options, logger: Logger) => Promise<*> renderer: (element: HTMLElement, options: Options, logger: Logger) => Promise<*>
): Promise<[HTMLIFrameElement, HTMLElement, ImageLoader<ImageElement>]> => { ): Promise<[HTMLIFrameElement, HTMLElement, ImageLoader<ImageElement>]> => {
const cloner = new DocumentCloner(referenceElement, options, logger, false, renderer); const cloner = new DocumentCloner(referenceElement, options, logger, false, renderer);
const scrollX = ownerDocument.defaultView.pageXOffset;
const scrollY = ownerDocument.defaultView.pageYOffset;
return createIframeContainer(ownerDocument, bounds).then(cloneIframeContainer => { return createIframeContainer(ownerDocument, bounds).then(cloneIframeContainer => {
const cloneWindow = cloneIframeContainer.contentWindow; const cloneWindow = cloneIframeContainer.contentWindow;
@ -422,16 +429,14 @@ export const cloneWindow = (
const iframeLoad = iframeLoader(cloneIframeContainer).then(() => { const iframeLoad = iframeLoader(cloneIframeContainer).then(() => {
cloner.scrolledElements.forEach(initNode); cloner.scrolledElements.forEach(initNode);
if (options.type === 'view') { cloneWindow.scrollTo(bounds.left, bounds.top);
cloneWindow.scrollTo(bounds.left, bounds.top); if (
if ( /(iPad|iPhone|iPod)/g.test(navigator.userAgent) &&
/(iPad|iPhone|iPod)/g.test(navigator.userAgent) && (cloneWindow.scrollY !== bounds.top || cloneWindow.scrollX !== bounds.left)
(cloneWindow.scrollY !== bounds.top || cloneWindow.scrollX !== bounds.left) ) {
) { documentClone.documentElement.style.top = -bounds.top + 'px';
documentClone.documentElement.style.top = -bounds.top + 'px'; documentClone.documentElement.style.left = -bounds.left + 'px';
documentClone.documentElement.style.left = -bounds.left + 'px'; documentClone.documentElement.style.position = 'absolute';
documentClone.documentElement.style.position = 'absolute';
}
} }
return cloner.clonedReferenceElement instanceof cloneWindow.HTMLElement || return cloner.clonedReferenceElement instanceof cloneWindow.HTMLElement ||
cloner.clonedReferenceElement instanceof ownerDocument.defaultView.HTMLElement || cloner.clonedReferenceElement instanceof ownerDocument.defaultView.HTMLElement ||
@ -451,7 +456,7 @@ export const cloneWindow = (
documentClone.open(); documentClone.open();
documentClone.write('<!DOCTYPE html><html></html>'); documentClone.write('<!DOCTYPE html><html></html>');
// Chrome scrolls the parent document for some reason after the write to the cloned window??? // Chrome scrolls the parent document for some reason after the write to the cloned window???
restoreOwnerScroll(referenceElement.ownerDocument, bounds.left, bounds.top); restoreOwnerScroll(referenceElement.ownerDocument, scrollX, scrollY);
documentClone.replaceChild( documentClone.replaceChild(
documentClone.adoptNode(cloner.documentElement), documentClone.adoptNode(cloner.documentElement),
documentClone.documentElement documentClone.documentElement

View File

@ -92,7 +92,7 @@ const testForeignObject = document => {
const img = new Image(); const img = new Image();
const greenImageSrc = canvas.toDataURL(); const greenImageSrc = canvas.toDataURL();
img.src = greenImageSrc; img.src = greenImageSrc;
const svg = createForeignObjectSVG(size, size, img); const svg = createForeignObjectSVG(size, size, 0, 0, img);
ctx.fillStyle = 'red'; ctx.fillStyle = 'red';
ctx.fillRect(0, 0, size, size); ctx.fillRect(0, 0, size, size);
@ -108,7 +108,7 @@ const testForeignObject = document => {
node.style.height = `${size}px`; node.style.height = `${size}px`;
// Firefox 55 does not render inline <img /> tags // Firefox 55 does not render inline <img /> tags
return isGreenPixel(data) return isGreenPixel(data)
? loadSerializedSVG(createForeignObjectSVG(size, size, node)) ? loadSerializedSVG(createForeignObjectSVG(size, size, 0, 0, node))
: Promise.reject(false); : Promise.reject(false);
}) })
.then(img => { .then(img => {

View File

@ -94,6 +94,8 @@ export default class NodeContainer {
this.index = index; this.index = index;
this.childNodes = []; this.childNodes = [];
const defaultView = node.ownerDocument.defaultView; const defaultView = node.ownerDocument.defaultView;
const scrollX = defaultView.pageXOffset;
const scrollY = defaultView.pageYOffset;
const style = defaultView.getComputedStyle(node, null); const style = defaultView.getComputedStyle(node, null);
const display = parseDisplay(style.display); const display = parseDisplay(style.display);
@ -138,7 +140,7 @@ export default class NodeContainer {
// TODO move bound retrieval for all nodes to a later stage? // TODO move bound retrieval for all nodes to a later stage?
if (node.tagName === 'IMG') { if (node.tagName === 'IMG') {
node.addEventListener('load', () => { node.addEventListener('load', () => {
this.bounds = parseBounds(node); this.bounds = parseBounds(node, scrollX, scrollY);
this.curvedBounds = parseBoundCurves( this.curvedBounds = parseBoundCurves(
this.bounds, this.bounds,
this.style.border, this.style.border,
@ -147,7 +149,9 @@ export default class NodeContainer {
}); });
} }
this.image = getImage(node, imageLoader); this.image = getImage(node, imageLoader);
this.bounds = IS_INPUT ? reformatInputBounds(parseBounds(node)) : parseBounds(node); this.bounds = IS_INPUT
? reformatInputBounds(parseBounds(node, scrollX, scrollY))
: parseBounds(node, scrollX, scrollY);
this.curvedBounds = parseBoundCurves( this.curvedBounds = parseBoundCurves(
this.bounds, this.bounds,
this.style.border, this.style.border,

View File

@ -46,6 +46,8 @@ export type RenderOptions = {
imageStore: ImageStore<ImageElement>, imageStore: ImageStore<ImageElement>,
fontMetrics: FontMetrics, fontMetrics: FontMetrics,
logger: Logger, logger: Logger,
x: number,
y: number,
width: number, width: number,
height: number height: number
}; };

View File

@ -33,16 +33,24 @@ export const parseTextBounds = (
const letterRendering = parent.style.letterSpacing !== 0 || hasUnicodeCharacters(value); const letterRendering = parent.style.letterSpacing !== 0 || hasUnicodeCharacters(value);
const textList = letterRendering ? codePoints.map(encodeCodePoint) : splitWords(codePoints); const textList = letterRendering ? codePoints.map(encodeCodePoint) : splitWords(codePoints);
const length = textList.length; const length = textList.length;
const defaultView = node.parentNode ? node.parentNode.ownerDocument.defaultView : null;
const scrollX = defaultView ? defaultView.pageXOffset : 0;
const scrollY = defaultView ? defaultView.pageYOffset : 0;
const textBounds = []; const textBounds = [];
let offset = 0; let offset = 0;
for (let i = 0; i < length; i++) { for (let i = 0; i < length; i++) {
let text = textList[i]; let text = textList[i];
if (parent.style.textDecoration !== TEXT_DECORATION.NONE || text.trim().length > 0) { if (parent.style.textDecoration !== TEXT_DECORATION.NONE || text.trim().length > 0) {
if (FEATURES.SUPPORT_RANGE_BOUNDS) { if (FEATURES.SUPPORT_RANGE_BOUNDS) {
textBounds.push(new TextBounds(text, getRangeBounds(node, offset, text.length))); textBounds.push(
new TextBounds(
text,
getRangeBounds(node, offset, text.length, scrollX, scrollY)
)
);
} else { } else {
const replacementNode = node.splitText(text.length); const replacementNode = node.splitText(text.length);
textBounds.push(new TextBounds(text, getWrapperBounds(node))); textBounds.push(new TextBounds(text, getWrapperBounds(node, scrollX, scrollY)));
node = replacementNode; node = replacementNode;
} }
} else if (!FEATURES.SUPPORT_RANGE_BOUNDS) { } else if (!FEATURES.SUPPORT_RANGE_BOUNDS) {
@ -53,13 +61,13 @@ export const parseTextBounds = (
return textBounds; return textBounds;
}; };
const getWrapperBounds = (node: Text): Bounds => { const getWrapperBounds = (node: Text, scrollX: number, scrollY: number): Bounds => {
const wrapper = node.ownerDocument.createElement('html2canvaswrapper'); const wrapper = node.ownerDocument.createElement('html2canvaswrapper');
wrapper.appendChild(node.cloneNode(true)); wrapper.appendChild(node.cloneNode(true));
const parentNode = node.parentNode; const parentNode = node.parentNode;
if (parentNode) { if (parentNode) {
parentNode.replaceChild(wrapper, node); parentNode.replaceChild(wrapper, node);
const bounds = parseBounds(wrapper); const bounds = parseBounds(wrapper, scrollX, scrollY);
if (wrapper.firstChild) { if (wrapper.firstChild) {
parentNode.replaceChild(wrapper.firstChild, wrapper); parentNode.replaceChild(wrapper.firstChild, wrapper);
} }
@ -68,11 +76,17 @@ const getWrapperBounds = (node: Text): Bounds => {
return new Bounds(0, 0, 0, 0); return new Bounds(0, 0, 0, 0);
}; };
const getRangeBounds = (node: Text, offset: number, length: number): Bounds => { const getRangeBounds = (
node: Text,
offset: number,
length: number,
scrollX: number,
scrollY: number
): Bounds => {
const range = node.ownerDocument.createRange(); const range = node.ownerDocument.createRange();
range.setStart(node, offset); range.setStart(node, offset);
range.setEnd(node, offset + length); range.setEnd(node, offset + length);
return Bounds.fromClientRect(range.getBoundingClientRect()); return Bounds.fromClientRect(range.getBoundingClientRect(), scrollX, scrollY);
}; };
const splitWords = (codePoints: Array<number>): Array<string> => { const splitWords = (codePoints: Array<number>): Array<string> => {

View File

@ -10,7 +10,7 @@ import Renderer from './Renderer';
import ForeignObjectRenderer from './renderer/ForeignObjectRenderer'; import ForeignObjectRenderer from './renderer/ForeignObjectRenderer';
import Feature from './Feature'; import Feature from './Feature';
import {Bounds, parseDocumentSize} from './Bounds'; import {Bounds} from './Bounds';
import {cloneWindow, DocumentCloner} from './Clone'; import {cloneWindow, DocumentCloner} from './Clone';
import {FontMetrics} from './Font'; import {FontMetrics} from './Font';
import Color, {TRANSPARENT} from './Color'; import Color, {TRANSPARENT} from './Color';
@ -23,14 +23,12 @@ export const renderElement = (
const ownerDocument = element.ownerDocument; const ownerDocument = element.ownerDocument;
const windowBounds = new Bounds( const windowBounds = new Bounds(
options.offsetX, options.scrollX,
options.offsetY, options.scrollY,
options.windowWidth, options.windowWidth,
options.windowHeight options.windowHeight
); );
const bounds = options.type === 'view' ? windowBounds : parseDocumentSize(ownerDocument);
// http://www.w3.org/TR/css3-background/#special-backgrounds // http://www.w3.org/TR/css3-background/#special-backgrounds
const documentBackgroundColor = ownerDocument.documentElement const documentBackgroundColor = ownerDocument.documentElement
? new Color(getComputedStyle(ownerDocument.documentElement).backgroundColor) ? new Color(getComputedStyle(ownerDocument.documentElement).backgroundColor)
@ -60,10 +58,17 @@ export const renderElement = (
return cloner.imageLoader.ready().then(() => { return cloner.imageLoader.ready().then(() => {
const renderer = new ForeignObjectRenderer(cloner.clonedReferenceElement); const renderer = new ForeignObjectRenderer(cloner.clonedReferenceElement);
return renderer.render({ return renderer.render({
bounds,
backgroundColor, backgroundColor,
logger, logger,
scale: options.scale scale: options.scale,
x: options.x,
y: options.y,
width: options.width,
height: options.height,
windowWidth: options.windowWidth,
windowHeight: options.windowHeight,
scrollX: options.scrollX,
scrollY: options.scrollY
}); });
}); });
})(new DocumentCloner(element, options, logger, true, renderElement)) })(new DocumentCloner(element, options, logger, true, renderElement))
@ -81,8 +86,6 @@ export const renderElement = (
const stack = NodeParser(clonedElement, imageLoader, logger); const stack = NodeParser(clonedElement, imageLoader, logger);
const clonedDocument = clonedElement.ownerDocument; const clonedDocument = clonedElement.ownerDocument;
const width = bounds.width;
const height = bounds.height;
if (backgroundColor === stack.container.style.background.backgroundColor) { if (backgroundColor === stack.container.style.background.backgroundColor) {
stack.container.style.background.backgroundColor = TRANSPARENT; stack.container.style.background.backgroundColor = TRANSPARENT;
@ -110,8 +113,10 @@ export const renderElement = (
imageStore, imageStore,
logger, logger,
scale: options.scale, scale: options.scale,
width, x: options.x,
height y: options.y,
width: options.width,
height: options.height
}; };
if (Array.isArray(options.target)) { if (Array.isArray(options.target)) {

View File

@ -6,6 +6,7 @@ import type {RenderTarget} from './Renderer';
import CanvasRenderer from './renderer/CanvasRenderer'; import CanvasRenderer from './renderer/CanvasRenderer';
import Logger from './Logger'; import Logger from './Logger';
import {renderElement} from './Window'; import {renderElement} from './Window';
import {parseBounds, parseDocumentSize} from './Bounds';
export type Options = { export type Options = {
async: ?boolean, async: ?boolean,
@ -17,11 +18,14 @@ export type Options = {
removeContainer: ?boolean, removeContainer: ?boolean,
scale: number, scale: number,
target: RenderTarget<*>, target: RenderTarget<*>,
type: ?string, width: number,
height: number,
x: number,
y: number,
scrollX: number,
scrollY: number,
windowWidth: number, windowWidth: number,
windowHeight: number, windowHeight: number
offsetX: number,
offsetY: number
}; };
const html2canvas = (element: HTMLElement, conf: ?Options): Promise<*> => { const html2canvas = (element: HTMLElement, conf: ?Options): Promise<*> => {
@ -37,6 +41,15 @@ const html2canvas = (element: HTMLElement, conf: ?Options): Promise<*> => {
const ownerDocument = element.ownerDocument; const ownerDocument = element.ownerDocument;
const defaultView = ownerDocument.defaultView; const defaultView = ownerDocument.defaultView;
const scrollX = defaultView.pageXOffset;
const scrollY = defaultView.pageYOffset;
const isDocument = element.tagName === 'HTML' || element.tagName === 'BODY';
const {width, height, left, top} = isDocument
? parseDocumentSize(ownerDocument)
: parseBounds(element, scrollX, scrollY);
const defaultOptions = { const defaultOptions = {
async: true, async: true,
allowTaint: false, allowTaint: false,
@ -45,11 +58,14 @@ const html2canvas = (element: HTMLElement, conf: ?Options): Promise<*> => {
removeContainer: true, removeContainer: true,
scale: defaultView.devicePixelRatio || 1, scale: defaultView.devicePixelRatio || 1,
target: new CanvasRenderer(config.canvas), target: new CanvasRenderer(config.canvas),
type: null, x: left,
y: top,
width: Math.ceil(width),
height: Math.ceil(height),
windowWidth: defaultView.innerWidth, windowWidth: defaultView.innerWidth,
windowHeight: defaultView.innerHeight, windowHeight: defaultView.innerHeight,
offsetX: defaultView.pageXOffset, scrollX: defaultView.pageXOffset,
offsetY: defaultView.pageYOffset scrollY: defaultView.pageYOffset
}; };
const result = renderElement(element, {...defaultOptions, ...config}, logger); const result = renderElement(element, {...defaultOptions, ...config}, logger);

View File

@ -37,9 +37,10 @@ export default class CanvasRenderer implements RenderTarget<HTMLCanvasElement> {
this.canvas.style.height = `${options.height}px`; this.canvas.style.height = `${options.height}px`;
this.ctx.scale(this.options.scale, this.options.scale); this.ctx.scale(this.options.scale, this.options.scale);
this.ctx.translate(-options.x, -options.y);
this.ctx.textBaseline = 'bottom'; this.ctx.textBaseline = 'bottom';
options.logger.log( options.logger.log(
`Canvas renderer initialized (${options.width}x${options.height}) with scale ${this `Canvas renderer initialized (${options.width}x${options.height} at ${options.x},${options.y}) with scale ${this
.options.scale}` .options.scale}`
); );
} }

View File

@ -12,31 +12,41 @@ export default class ForeignObjectRenderer {
this.options = options; this.options = options;
this.canvas = document.createElement('canvas'); this.canvas = document.createElement('canvas');
this.ctx = this.canvas.getContext('2d'); this.ctx = this.canvas.getContext('2d');
this.canvas.width = Math.floor(options.bounds.width * options.scale); this.canvas.width = Math.floor(options.width * options.scale);
this.canvas.height = Math.floor(options.bounds.height * options.scale); this.canvas.height = Math.floor(options.height * options.scale);
this.canvas.style.width = `${options.bounds.width}px`; this.canvas.style.width = `${options.width}px`;
this.canvas.style.height = `${options.bounds.height}px`; this.canvas.style.height = `${options.height}px`;
this.ctx.scale(this.options.scale, this.options.scale); this.ctx.scale(this.options.scale, this.options.scale);
options.logger.log(`ForeignObject renderer initialized with scale ${this.options.scale}`); options.logger.log(
`ForeignObject renderer initialized (${options.width}x${options.height} at ${options.x},${options.y}) with scale ${this
.options.scale}`
);
const svg = createForeignObjectSVG( const svg = createForeignObjectSVG(
options.bounds.width, Math.max(options.windowWidth, options.width),
options.bounds.height, Math.max(options.windowHeight, options.height),
options.scrollX,
options.scrollY,
this.element this.element
); );
return loadSerializedSVG(svg).then(img => { return loadSerializedSVG(svg).then(img => {
if (options.backgroundColor) { if (options.backgroundColor) {
this.ctx.fillStyle = options.backgroundColor.toString(); this.ctx.fillStyle = options.backgroundColor.toString();
this.ctx.fillRect(0, 0, options.bounds.width, options.bounds.height); this.ctx.fillRect(0, 0, options.width, options.height);
} }
this.ctx.drawImage(img, 0, 0); this.ctx.drawImage(img, -options.x, -options.y);
return this.canvas; return this.canvas;
}); });
} }
} }
export const createForeignObjectSVG = (width: number, height: number, node: Node) => { export const createForeignObjectSVG = (
width: number,
height: number,
x: number,
y: number,
node: Node
) => {
const xmlns = 'http://www.w3.org/2000/svg'; const xmlns = 'http://www.w3.org/2000/svg';
const svg = document.createElementNS(xmlns, 'svg'); const svg = document.createElementNS(xmlns, 'svg');
const foreignObject = document.createElementNS(xmlns, 'foreignObject'); const foreignObject = document.createElementNS(xmlns, 'foreignObject');
@ -45,6 +55,8 @@ export const createForeignObjectSVG = (width: number, height: number, node: Node
foreignObject.setAttributeNS(null, 'width', '100%'); foreignObject.setAttributeNS(null, 'width', '100%');
foreignObject.setAttributeNS(null, 'height', '100%'); foreignObject.setAttributeNS(null, 'height', '100%');
foreignObject.setAttributeNS(null, 'x', x);
foreignObject.setAttributeNS(null, 'y', y);
foreignObject.setAttributeNS(null, 'externalResourcesRequired', 'true'); foreignObject.setAttributeNS(null, 'externalResourcesRequired', 'true');
svg.appendChild(foreignObject); svg.appendChild(foreignObject);

View File

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<title>crop test</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script>
h2cOptions = {
x: 250,
y: 250,
width: 100,
height: 100
};
</script>
<script type="text/javascript" src="../../test.js"></script>
<style>
#div1 {
position: absolute;
left: 250px;
top: 250px;
width: 100px;
height: 100px;
background: green;
}
body, html {
background: red;
}
</style>
</head>
<body>
<div id="div1">
great success
</div>
</body>
</html>

View File

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<title>element render test</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script type="text/javascript" src="../../test.js"></script>
<style>
#div1 {
position: absolute;
left: 250px;
top: 250px;
width: 100px;
height: 100px;
background: green;
}
body, html {
background: red;
}
</style>
</head>
<body>
<div id="div1">
great success
</div>
<script>
var forceElement = document.querySelector('#div1');
h2cSelector = forceElement;
</script>
</body>
</html>

View File

@ -0,0 +1,54 @@
<!DOCTYPE html>
<html>
<head>
<title>scroll test</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script>
h2cOptions = {
x: 250,
y: 250,
width: 200,
height: 100,
scrollX: 250,
scrollY: 250
};
</script>
<script type="text/javascript" src="../../test.js"></script>
<style>
#div1 {
position: absolute;
left: 350px;
top: 250px;
width: 100px;
height: 100px;
background: green;
}
#div2 {
position: fixed;
left: 0;
top: 0;
width: 100px;
height: 100px;
background: lightblue;
}
body, html {
background: red;
height: 4000px;
width: 4000px;
}
</style>
</head>
<body>
<div id="div1">
great success
</div>
<div id="div2">
fixed great success
</div>
</body>
</html>

View File

@ -143,7 +143,7 @@ var REFTEST = window.location.search.indexOf('reftest') !== -1;
}; };
})(jQuery); })(jQuery);
h2cSelector = [document.documentElement]; h2cSelector = typeof h2cSelector === 'undefined' ? [document.documentElement] : h2cSelector;
if (window.setUp) { if (window.setUp) {
window.setUp(); window.setUp();

View File

@ -115,7 +115,7 @@ const assertPath = (result, expected, desc) => {
}); });
it('Should render untainted canvas', () => { it('Should render untainted canvas', () => {
return testContainer.contentWindow return testContainer.contentWindow
.html2canvas(testContainer.contentWindow.document.documentElement, { .html2canvas(testContainer.contentWindow.forceElement || testContainer.contentWindow.document.documentElement, {
removeContainer: true, removeContainer: true,
backgroundColor: '#ffffff', backgroundColor: '#ffffff',
proxy: 'http://localhost:8081/proxy', proxy: 'http://localhost:8081/proxy',