Compare commits

...

13 Commits

20 changed files with 1203 additions and 84 deletions

View File

@ -48,7 +48,7 @@ deploy:
- provider: pages
skip_cleanup: true
local_dir: www/public
target_branch: gh-pages-test
target_branch: gh-pages
fqdn: html2canvas.hertzen.com
github_token:
secure: "PowO/Jat660k3gHcjgI6DlJz15RM7pLUu11UPsLCtYJ8ZwodppE6Keg0DfVkSFSIZttZor+UssDwP/WOEqfZNLqmXbcj3Gec4xolohet/GOe0KJKKuF/HgggbcxumopxMX6sMVePlMBpkLpHh7tgEAEHBWTlzC1c1a7Xa48fZ7k="

View File

@ -1,5 +1,11 @@
### Changelog ###
#### v1.0.0-alpha5 - 21.12.2017 ####
* Fix underline positioning
* Fix canvas rendering on Chrome
* Fix overflow: auto
* Added support for rendering list-style
#### v1.0.0-alpha4 - 12.12.2017 ####
* Fix rendering with multiple fonts defined (Fix #796)
* Add support for radial-gradients

View File

@ -3,7 +3,7 @@ html2canvas
[Homepage](https://html2canvas.hertzen.com) | [Downloads](https://github.com/niklasvh/html2canvas/releases) | [Questions](http://stackoverflow.com/questions/tagged/html2canvas?sort=newest) | [Donate](https://www.gittip.com/niklasvh/)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/niklasvh/html2canvas?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Build Status](https://travis-ci.org/niklasvh/html2canvas.png)](https://travis-ci.org/niklasvh/html2canvas)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/niklasvh/html2canvas?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Build Status](https://travis-ci.org/niklasvh/html2canvas.svg)](https://travis-ci.org/niklasvh/html2canvas)
#### JavaScript HTML renderer ####

View File

@ -21,6 +21,7 @@ These are all of the available configuration options.
| proxy | `null` | Url to the [proxy](/proxy/) which is to be used for loading cross-origin images. If left empty, cross-origin images won't be loaded.
| removeContainer | `true` | Whether to cleanup the cloned DOM elements html2canvas creates temporarily
| scale | `window.devicePixelRatio` | The scale to use for rendering. Defaults to the browsers device pixel ratio.
| useCORS | `false` | Whether to attempt to load images from a server using CORS
| width | `Element` width | The width of the `canvas`
| height | `Element` height | The height of the `canvas`
| x | `Element` x-offset | Crop canvas x-coordinate
@ -29,3 +30,5 @@ These are all of the available configuration options.
| scrollY | `Element` scrollY | The y-scroll position to used when rendering element, (for example if the Element uses `position: fixed`)
| windowWidth | `Window.innerWidth` | Window width to use when rendering `Element`, which may affect things like Media queries
| windowHeight | `Window.innerHeight` | Window height to use when rendering `Element`, which may affect things like Media queries
If you wish to exclude certain `Element`s from getting rendered, you can add a `data-html2canvas-ignore` attribute to those elements and html2canvas will exclude them from the rendering.

View File

@ -3,7 +3,7 @@
"name": "html2canvas",
"description": "Screenshots with JavaScript",
"main": "dist/npm/index.js",
"version": "1.0.0-alpha.4",
"version": "1.0.0-alpha.5",
"author": {
"name": "Niklas von Hertzen",
"email": "niklasvh@gmail.com",

View File

@ -182,6 +182,7 @@ export class DocumentCloner {
removeContainer: this.options.removeContainer,
scale: this.options.scale,
foreignObjectRendering: this.options.foreignObjectRendering,
useCORS: this.options.useCORS,
target: new CanvasRenderer(),
width,
height,

674
src/ListItem.js Normal file
View File

@ -0,0 +1,674 @@
/* @flow */
'use strict';
import type ResourceLoader from './ResourceLoader';
import type {ListStyleType} from './parsing/listStyle';
import {copyCSSStyles, contains} from './Util';
import NodeContainer from './NodeContainer';
import TextContainer from './TextContainer';
import {LIST_STYLE_POSITION, LIST_STYLE_TYPE} from './parsing/listStyle';
import {fromCodePoint} from './Unicode';
// Margin between the enumeration and the list item content
const MARGIN_RIGHT = 7;
const ancestorTypes = ['OL', 'UL', 'MENU'];
export const getListOwner = (container: NodeContainer): ?NodeContainer => {
let parent = container.parent;
if (!parent) {
return null;
}
do {
let isAncestor = ancestorTypes.indexOf(parent.tagName) !== -1;
if (isAncestor) {
return parent;
}
parent = parent.parent;
} while (parent);
return container.parent;
};
export const inlineListItemElement = (
node: HTMLElement,
container: NodeContainer,
resourceLoader: ResourceLoader
): void => {
const listStyle = container.style.listStyle;
if (!listStyle) {
return;
}
const style = node.ownerDocument.defaultView.getComputedStyle(node, null);
const wrapper = node.ownerDocument.createElement('html2canvaswrapper');
copyCSSStyles(style, wrapper);
wrapper.style.position = 'absolute';
wrapper.style.bottom = 'auto';
wrapper.style.display = 'block';
wrapper.style.letterSpacing = 'normal';
switch (listStyle.listStylePosition) {
case LIST_STYLE_POSITION.OUTSIDE:
wrapper.style.left = 'auto';
wrapper.style.right = `${node.ownerDocument.defaultView.innerWidth -
container.bounds.left -
container.style.margin[1].getAbsoluteValue(container.bounds.width) +
MARGIN_RIGHT}px`;
wrapper.style.textAlign = 'right';
break;
case LIST_STYLE_POSITION.INSIDE:
wrapper.style.left = `${container.bounds.left -
container.style.margin[3].getAbsoluteValue(container.bounds.width)}px`;
wrapper.style.right = 'auto';
wrapper.style.textAlign = 'left';
break;
}
let text;
const MARGIN_TOP = container.style.margin[0].getAbsoluteValue(container.bounds.width);
const styleImage = listStyle.listStyleImage;
if (styleImage) {
if (styleImage.method === 'url') {
const image = node.ownerDocument.createElement('img');
image.src = styleImage.args[0];
wrapper.style.top = `${container.bounds.top - MARGIN_TOP}px`;
wrapper.style.width = 'auto';
wrapper.style.height = 'auto';
wrapper.appendChild(image);
} else {
const size = parseFloat(container.style.font.fontSize) * 0.5;
wrapper.style.top = `${container.bounds.top -
MARGIN_TOP +
container.bounds.height -
1.5 * size}px`;
wrapper.style.width = `${size}px`;
wrapper.style.height = `${size}px`;
wrapper.style.backgroundImage = style.listStyleImage;
}
} else if (typeof container.listIndex === 'number') {
text = node.ownerDocument.createTextNode(
createCounterText(container.listIndex, listStyle.listStyleType)
);
wrapper.appendChild(text);
wrapper.style.top = `${container.bounds.top - MARGIN_TOP}px`;
}
// $FlowFixMe
const body: HTMLBodyElement = node.ownerDocument.body;
body.appendChild(wrapper);
if (text) {
container.childNodes.push(TextContainer.fromTextNode(text, container));
body.removeChild(wrapper);
} else {
// $FlowFixMe
container.childNodes.push(new NodeContainer(wrapper, container, resourceLoader, 0));
}
};
const ROMAN_UPPER = {
integers: [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1],
values: ['M', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV', 'I']
};
const ARMENIAN = {
integers: [
9000,
8000,
7000,
6000,
5000,
4000,
3000,
2000,
1000,
900,
800,
700,
600,
500,
400,
300,
200,
100,
90,
80,
70,
60,
50,
40,
30,
20,
10,
9,
8,
7,
6,
5,
4,
3,
2,
1
],
values: [
'Ք',
'Փ',
'Ւ',
'Ց',
'Ր',
'Տ',
'Վ',
'Ս',
'Ռ',
'Ջ',
'Պ',
'Չ',
'Ո',
'Շ',
'Ն',
'Յ',
'Մ',
'Ճ',
'Ղ',
'Ձ',
'Հ',
'Կ',
'Ծ',
'Խ',
'Լ',
'Ի',
'Ժ',
'Թ',
'Ը',
'Է',
'Զ',
'Ե',
'Դ',
'Գ',
'Բ',
'Ա'
]
};
const HEBREW = {
integers: [
10000,
9000,
8000,
7000,
6000,
5000,
4000,
3000,
2000,
1000,
400,
300,
200,
100,
90,
80,
70,
60,
50,
40,
30,
20,
19,
18,
17,
16,
15,
10,
9,
8,
7,
6,
5,
4,
3,
2,
1
],
values: [
'י׳',
'ט׳',
'ח׳',
'ז׳',
'ו׳',
'ה׳',
'ד׳',
'ג׳',
'ב׳',
'א׳',
'ת',
'ש',
'ר',
'ק',
'צ',
'פ',
'ע',
'ס',
'נ',
'מ',
'ל',
'כ',
'יט',
'יח',
'יז',
'טז',
'טו',
'י',
'ט',
'ח',
'ז',
'ו',
'ה',
'ד',
'ג',
'ב',
'א'
]
};
const GEORGIAN = {
integers: [
10000,
9000,
8000,
7000,
6000,
5000,
4000,
3000,
2000,
1000,
900,
800,
700,
600,
500,
400,
300,
200,
100,
90,
80,
70,
60,
50,
40,
30,
20,
10,
9,
8,
7,
6,
5,
4,
3,
2,
1
],
values: [
'ჵ',
'ჰ',
'ჯ',
'ჴ',
'ხ',
'ჭ',
'წ',
'ძ',
'ც',
'ჩ',
'შ',
'',
'ღ',
'ქ',
'ფ',
'ჳ',
'ტ',
'ს',
'რ',
'ჟ',
'პ',
'ო',
'ჲ',
'ნ',
'მ',
'ლ',
'კ',
'ი',
'თ',
'ჱ',
'ზ',
'ვ',
'ე',
'დ',
'გ',
'ბ',
'ა'
]
};
const createAdditiveCounter = (
value: number,
min: number,
max: number,
symbols,
fallback: ListStyleType,
suffix: string = '. '
) => {
if (value < min || value > max) {
return createCounterText(value, fallback);
}
return (
symbols.integers.reduce((string, integer, index) => {
while (value >= integer) {
value -= integer;
string += symbols.values[index];
}
return string;
}, '') + suffix
);
};
const createCounterStyleWithSymbolResolver = (
value: number,
codePointRangeLength: number,
isNumeric: boolean,
resolver
): string => {
let string = '';
do {
if (!isNumeric) {
value--;
}
string = resolver(value) + string;
value /= codePointRangeLength;
} while (value * codePointRangeLength >= codePointRangeLength);
return string;
};
const createCounterStyleFromRange = (
value: number,
codePointRangeStart: number,
codePointRangeEnd: number,
isNumeric: boolean,
suffix: string = '. '
): string => {
const codePointRangeLength = codePointRangeEnd - codePointRangeStart + 1;
return (
(value < 0 ? '-' : '') +
(createCounterStyleWithSymbolResolver(
Math.abs(value),
codePointRangeLength,
isNumeric,
codePoint =>
fromCodePoint(Math.floor(codePoint % codePointRangeLength) + codePointRangeStart)
) +
suffix)
);
};
const createCounterStyleFromSymbols = (
value: number,
symbols: string,
suffix: string = '. '
): string => {
const codePointRangeLength = symbols.length;
return (
createCounterStyleWithSymbolResolver(
Math.abs(value),
codePointRangeLength,
false,
codePoint => symbols[Math.floor(codePoint % codePointRangeLength)]
) + suffix
);
};
const CJK_ZEROS = 1 << 0;
const CJK_TEN_COEFFICIENTS = 1 << 1;
const CJK_TEN_HIGH_COEFFICIENTS = 1 << 2;
const CJK_HUNDRED_COEFFICIENTS = 1 << 3;
const createCJKCounter = (
value: number,
numbers: string,
multipliers: string,
negativeSign: string,
suffix: string,
flags: number
): string => {
if (value < -9999 || value > 9999) {
return createCounterText(value, LIST_STYLE_TYPE.CJK_DECIMAL);
}
let tmp = Math.abs(value);
let string = suffix;
if (tmp === 0) {
return numbers[0] + string;
}
for (let digit = 0; tmp > 0 && digit <= 4; digit++) {
let coefficient = tmp % 10;
if (coefficient === 0 && contains(flags, CJK_ZEROS) && string !== '') {
string = numbers[coefficient] + string;
} else if (
coefficient > 1 ||
(coefficient === 1 && digit === 0) ||
(coefficient === 1 && digit === 1 && contains(flags, CJK_TEN_COEFFICIENTS)) ||
(coefficient === 1 &&
digit === 1 &&
contains(flags, CJK_TEN_HIGH_COEFFICIENTS) &&
value > 100) ||
(coefficient === 1 && digit > 1 && contains(flags, CJK_HUNDRED_COEFFICIENTS))
) {
string = numbers[coefficient] + (digit > 0 ? multipliers[digit - 1] : '') + string;
} else if (coefficient === 1 && digit > 0) {
string = multipliers[digit - 1] + string;
}
tmp = Math.floor(tmp / 10);
}
return (value < 0 ? negativeSign : '') + string;
};
const CHINESE_INFORMAL_MULTIPLIERS = '十百千萬';
const CHINESE_FORMAL_MULTIPLIERS = '拾佰仟萬';
const JAPANESE_NEGATIVE = 'マイナス';
const KOREAN_NEGATIVE = '마이너스 ';
const createCounterText = (value: number, type: ListStyleType): string => {
switch (type) {
case LIST_STYLE_TYPE.DISC:
return '•';
case LIST_STYLE_TYPE.CIRCLE:
return '◦';
case LIST_STYLE_TYPE.SQUARE:
return '◾';
case LIST_STYLE_TYPE.DECIMAL_LEADING_ZERO:
const string = createCounterStyleFromRange(value, 48, 57, true);
return string.length < 4 ? `0${string}` : string;
case LIST_STYLE_TYPE.CJK_DECIMAL:
return createCounterStyleFromSymbols(value, '〇一二三四五六七八九', '、');
case LIST_STYLE_TYPE.LOWER_ROMAN:
return createAdditiveCounter(
value,
1,
3999,
ROMAN_UPPER,
LIST_STYLE_TYPE.DECIMAL
).toLowerCase();
case LIST_STYLE_TYPE.UPPER_ROMAN:
return createAdditiveCounter(value, 1, 3999, ROMAN_UPPER, LIST_STYLE_TYPE.DECIMAL);
case LIST_STYLE_TYPE.LOWER_GREEK:
return createCounterStyleFromRange(value, 945, 969, false);
case LIST_STYLE_TYPE.LOWER_ALPHA:
return createCounterStyleFromRange(value, 97, 122, false);
case LIST_STYLE_TYPE.UPPER_ALPHA:
return createCounterStyleFromRange(value, 65, 90, false);
case LIST_STYLE_TYPE.ARABIC_INDIC:
return createCounterStyleFromRange(value, 1632, 1641, true);
case LIST_STYLE_TYPE.ARMENIAN:
case LIST_STYLE_TYPE.UPPER_ARMENIAN:
return createAdditiveCounter(value, 1, 9999, ARMENIAN, LIST_STYLE_TYPE.DECIMAL);
case LIST_STYLE_TYPE.LOWER_ARMENIAN:
return createAdditiveCounter(
value,
1,
9999,
ARMENIAN,
LIST_STYLE_TYPE.DECIMAL
).toLowerCase();
case LIST_STYLE_TYPE.BENGALI:
return createCounterStyleFromRange(value, 2534, 2543, true);
case LIST_STYLE_TYPE.CAMBODIAN:
case LIST_STYLE_TYPE.KHMER:
return createCounterStyleFromRange(value, 6112, 6121, true);
case LIST_STYLE_TYPE.CJK_EARTHLY_BRANCH:
return createCounterStyleFromSymbols(value, '子丑寅卯辰巳午未申酉戌亥', '、');
case LIST_STYLE_TYPE.CJK_HEAVENLY_STEM:
return createCounterStyleFromSymbols(value, '甲乙丙丁戊己庚辛壬癸', '、');
case LIST_STYLE_TYPE.CJK_IDEOGRAPHIC:
case LIST_STYLE_TYPE.TRAD_CHINESE_INFORMAL:
return createCJKCounter(
value,
'零一二三四五六七八九',
CHINESE_INFORMAL_MULTIPLIERS,
'負',
'、',
CJK_TEN_COEFFICIENTS | CJK_TEN_HIGH_COEFFICIENTS | CJK_HUNDRED_COEFFICIENTS
);
case LIST_STYLE_TYPE.TRAD_CHINESE_FORMAL:
return createCJKCounter(
value,
'零壹貳參肆伍陸柒捌玖',
CHINESE_FORMAL_MULTIPLIERS,
'負',
'、',
CJK_ZEROS |
CJK_TEN_COEFFICIENTS |
CJK_TEN_HIGH_COEFFICIENTS |
CJK_HUNDRED_COEFFICIENTS
);
case LIST_STYLE_TYPE.SIMP_CHINESE_INFORMAL:
return createCJKCounter(
value,
'零一二三四五六七八九',
CHINESE_INFORMAL_MULTIPLIERS,
'负',
'、',
CJK_TEN_COEFFICIENTS | CJK_TEN_HIGH_COEFFICIENTS | CJK_HUNDRED_COEFFICIENTS
);
case LIST_STYLE_TYPE.SIMP_CHINESE_FORMAL:
return createCJKCounter(
value,
'零壹贰叁肆伍陆柒捌玖',
CHINESE_FORMAL_MULTIPLIERS,
'负',
'、',
CJK_ZEROS |
CJK_TEN_COEFFICIENTS |
CJK_TEN_HIGH_COEFFICIENTS |
CJK_HUNDRED_COEFFICIENTS
);
case LIST_STYLE_TYPE.JAPANESE_INFORMAL:
return createCJKCounter(value, '〇一二三四五六七八九', '十百千万', JAPANESE_NEGATIVE, '、', 0);
case LIST_STYLE_TYPE.JAPANESE_FORMAL:
return createCJKCounter(
value,
'零壱弐参四伍六七八九',
'拾百千万',
JAPANESE_NEGATIVE,
'、',
CJK_ZEROS | CJK_TEN_COEFFICIENTS | CJK_TEN_HIGH_COEFFICIENTS
);
case LIST_STYLE_TYPE.KOREAN_HANGUL_FORMAL:
return createCJKCounter(
value,
'영일이삼사오육칠팔구',
'십백천만',
KOREAN_NEGATIVE,
', ',
CJK_ZEROS | CJK_TEN_COEFFICIENTS | CJK_TEN_HIGH_COEFFICIENTS
);
case LIST_STYLE_TYPE.KOREAN_HANJA_INFORMAL:
return createCJKCounter(value, '零一二三四五六七八九', '十百千萬', KOREAN_NEGATIVE, ', ', 0);
case LIST_STYLE_TYPE.KOREAN_HANJA_FORMAL:
return createCJKCounter(
value,
'零壹貳參四五六七八九',
'拾百千',
KOREAN_NEGATIVE,
', ',
CJK_ZEROS | CJK_TEN_COEFFICIENTS | CJK_TEN_HIGH_COEFFICIENTS
);
case LIST_STYLE_TYPE.DEVANAGARI:
return createCounterStyleFromRange(value, 0x966, 0x96f, true);
case LIST_STYLE_TYPE.GEORGIAN:
return createAdditiveCounter(value, 1, 19999, GEORGIAN, LIST_STYLE_TYPE.DECIMAL);
case LIST_STYLE_TYPE.GUJARATI:
return createCounterStyleFromRange(value, 0xae6, 0xaef, true);
case LIST_STYLE_TYPE.GURMUKHI:
return createCounterStyleFromRange(value, 0xa66, 0xa6f, true);
case LIST_STYLE_TYPE.HEBREW:
return createAdditiveCounter(value, 1, 10999, HEBREW, LIST_STYLE_TYPE.DECIMAL);
case LIST_STYLE_TYPE.HIRAGANA:
return createCounterStyleFromSymbols(
value,
'あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわゐゑをん'
);
case LIST_STYLE_TYPE.HIRAGANA_IROHA:
return createCounterStyleFromSymbols(
value,
'いろはにほへとちりぬるをわかよたれそつねならむうゐのおくやまけふこえてあさきゆめみしゑひもせす'
);
case LIST_STYLE_TYPE.KANNADA:
return createCounterStyleFromRange(value, 0xce6, 0xcef, true);
case LIST_STYLE_TYPE.KATAKANA:
return createCounterStyleFromSymbols(
value,
'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヰヱヲン',
'、'
);
case LIST_STYLE_TYPE.KATAKANA_IROHA:
return createCounterStyleFromSymbols(
value,
'イロハニホヘトチリヌルヲワカヨタレソツネナラムウヰノオクヤマケフコエテアサキユメミシヱヒモセス',
'、'
);
case LIST_STYLE_TYPE.LAO:
return createCounterStyleFromRange(value, 0xed0, 0xed9, true);
case LIST_STYLE_TYPE.MONGOLIAN:
return createCounterStyleFromRange(value, 0x1810, 0x1819, true);
case LIST_STYLE_TYPE.MYANMAR:
return createCounterStyleFromRange(value, 0x1040, 0x1049, true);
case LIST_STYLE_TYPE.ORIYA:
return createCounterStyleFromRange(value, 0xb66, 0xb6f, true);
case LIST_STYLE_TYPE.PERSIAN:
return createCounterStyleFromRange(value, 0x6f0, 0x6f9, true);
case LIST_STYLE_TYPE.TAMIL:
return createCounterStyleFromRange(value, 0xbe6, 0xbef, true);
case LIST_STYLE_TYPE.TELUGU:
return createCounterStyleFromRange(value, 0xc66, 0xc6f, true);
case LIST_STYLE_TYPE.THAI:
return createCounterStyleFromRange(value, 0xe50, 0xe59, true);
case LIST_STYLE_TYPE.TIBETAN:
return createCounterStyleFromRange(value, 0xf20, 0xf29, true);
case LIST_STYLE_TYPE.DECIMAL:
default:
return createCounterStyleFromRange(value, 48, 57, true);
}
};

View File

@ -7,6 +7,8 @@ import type {BorderRadius} from './parsing/borderRadius';
import type {DisplayBit} from './parsing/display';
import type {Float} from './parsing/float';
import type {Font} from './parsing/font';
import type {ListStyle} from './parsing/listStyle';
import type {Margin} from './parsing/margin';
import type {Overflow} from './parsing/overflow';
import type {Padding} from './parsing/padding';
import type {Position} from './parsing/position';
@ -32,6 +34,8 @@ import {parseDisplay, DISPLAY} from './parsing/display';
import {parseCSSFloat, FLOAT} from './parsing/float';
import {parseFont} from './parsing/font';
import {parseLetterSpacing} from './parsing/letterSpacing';
import {parseListStyle} from './parsing/listStyle';
import {parseMargin} from './parsing/margin';
import {parseOverflow, OVERFLOW} from './parsing/overflow';
import {parsePadding} from './parsing/padding';
import {parsePosition, POSITION} from './parsing/position';
@ -50,6 +54,7 @@ import {
getInputBorderRadius,
reformatInputBounds
} from './Input';
import {getListOwner} from './ListItem';
type StyleDeclaration = {
background: Background,
@ -60,6 +65,8 @@ type StyleDeclaration = {
float: Float,
font: Font,
letterSpacing: number,
listStyle: ListStyle | null,
margin: Margin,
opacity: number,
overflow: Overflow,
padding: Padding,
@ -79,10 +86,14 @@ export default class NodeContainer {
parent: ?NodeContainer;
style: StyleDeclaration;
childNodes: Array<TextContainer | Path>;
listItems: Array<NodeContainer>;
listIndex: ?number;
listStart: ?number;
bounds: Bounds;
curvedBounds: BoundCurves;
image: ?string;
index: number;
tagName: string;
constructor(
node: HTMLElement | SVGSVGElement,
@ -91,8 +102,13 @@ export default class NodeContainer {
index: number
) {
this.parent = parent;
this.tagName = node.tagName;
this.index = index;
this.childNodes = [];
this.listItems = [];
if (typeof node.start === 'number') {
this.listStart = node.start;
}
const defaultView = node.ownerDocument.defaultView;
const scrollX = defaultView.pageXOffset;
const scrollY = defaultView.pageYOffset;
@ -117,6 +133,8 @@ export default class NodeContainer {
float: parseCSSFloat(style.float),
font: parseFont(style),
letterSpacing: parseLetterSpacing(style.letterSpacing),
listStyle: display === DISPLAY.LIST_ITEM ? parseListStyle(style) : null,
margin: parseMargin(style),
opacity: parseFloat(style.opacity),
overflow:
INPUT_TAGS.indexOf(node.tagName) === -1
@ -137,6 +155,20 @@ export default class NodeContainer {
node.style.transform = 'matrix(1,0,0,1,0,0)';
}
if (display === DISPLAY.LIST_ITEM) {
const listOwner = getListOwner(this);
if (listOwner) {
const listIndex = listOwner.listItems.length;
listOwner.listItems.push(this);
this.listIndex =
node.hasAttribute('value') && typeof node.value === 'number'
? node.value
: listIndex === 0
? typeof listOwner.listStart === 'number' ? listOwner.listStart : 1
: listOwner.listItems[listIndex - 1].listIndex + 1;
}
}
// TODO move bound retrieval for all nodes to a later stage?
if (node.tagName === 'IMG') {
node.addEventListener('load', () => {
@ -170,8 +202,7 @@ export default class NodeContainer {
}
getClipPaths(): Array<Path> {
const parentClips = this.parent ? this.parent.getClipPaths() : [];
const isClipped =
this.style.overflow === OVERFLOW.HIDDEN || this.style.overflow === OVERFLOW.SCROLL;
const isClipped = this.style.overflow !== OVERFLOW.VISIBLE;
return isClipped
? parentClips.concat([calculatePaddingBoxPath(this.curvedBounds)])

View File

@ -6,6 +6,7 @@ import StackingContext from './StackingContext';
import NodeContainer from './NodeContainer';
import TextContainer from './TextContainer';
import {inlineInputElement, inlineTextAreaElement, inlineSelectElement} from './Input';
import {inlineListItemElement} from './ListItem';
export const NodeParser = (
node: HTMLElement,
@ -71,6 +72,11 @@ const parseNodeTree = (
} else if (childNode.tagName === 'SELECT') {
// $FlowFixMe
inlineSelectElement(childNode, container);
} else if (
container.style.listStyle &&
container.style.listStyle.listStyleType !== 'none'
) {
inlineListItemElement(childNode, container, resourceLoader);
}
const SHOULD_TRAVERSE_CHILDREN = childNode.tagName !== 'TEXTAREA';

32
src/Unicode.js Normal file
View File

@ -0,0 +1,32 @@
/* @flow */
'use strict';
export const fromCodePoint = (...codePoints: Array<number>): string => {
if (String.fromCodePoint) {
return String.fromCodePoint(...codePoints);
}
const length = codePoints.length;
if (!length) {
return '';
}
const codeUnits = [];
let index = -1;
let result = '';
while (++index < length) {
let codePoint = codePoints[index];
if (codePoint <= 0xffff) {
codeUnits.push(codePoint);
} else {
codePoint -= 0x10000;
codeUnits.push((codePoint >> 10) + 0xd800, codePoint % 0x400 + 0xdc00);
}
if (index + 1 === length || codeUnits.length > 0x4000) {
result += String.fromCharCode(...codeUnits);
codeUnits.length = 0;
}
}
return result;
};

View File

@ -97,16 +97,6 @@ export const renderElement = (
}
return resourceLoader.ready().then(imageStore => {
if (options.removeContainer === true) {
if (container.parentNode) {
container.parentNode.removeChild(container);
} else if (__DEV__) {
logger.log(
`Cannot detach cloned iframe as it is not in the DOM anymore`
);
}
}
const fontMetrics = new FontMetrics(clonedDocument);
if (__DEV__) {
logger.log(`Starting renderer`);
@ -133,7 +123,18 @@ export const renderElement = (
);
} else {
const renderer = new Renderer(options.target, renderOptions);
return renderer.render(stack);
const canvas = renderer.render(stack);
if (options.removeContainer === true) {
if (container.parentNode) {
container.parentNode.removeChild(container);
} else if (__DEV__) {
logger.log(
`Cannot detach cloned iframe as it is not in the DOM anymore`
);
}
}
return canvas;
}
});
})

View File

@ -20,6 +20,7 @@ export type Options = {
removeContainer: ?boolean,
scale: number,
target: RenderTarget<*>,
useCORS: boolean,
width: number,
height: number,
x: number,
@ -72,6 +73,7 @@ const html2canvas = (element: HTMLElement, conf: ?Options): Promise<*> => {
foreignObjectRendering: false,
scale: defaultView.devicePixelRatio || 1,
target: new CanvasRenderer(config.canvas),
useCORS: false,
x: left,
y: top,
width: Math.ceil(width),

209
src/parsing/listStyle.js Normal file
View File

@ -0,0 +1,209 @@
/* @flow */
'use strict';
import type {BackgroundSource} from './background';
import {parseBackgroundImage} from './background';
export type ListStyle = {
listStyleType: ListStyleType,
listStyleImage: ?BackgroundSource,
listStylePosition: ListStylePosition
};
export const LIST_STYLE_POSITION = {
INSIDE: 0,
OUTSIDE: 1
};
export const LIST_STYLE_TYPE = {
NONE: -1,
DISC: 0,
CIRCLE: 1,
SQUARE: 2,
DECIMAL: 3,
CJK_DECIMAL: 4,
DECIMAL_LEADING_ZERO: 5,
LOWER_ROMAN: 6,
UPPER_ROMAN: 7,
LOWER_GREEK: 8,
LOWER_ALPHA: 9,
UPPER_ALPHA: 10,
ARABIC_INDIC: 11,
ARMENIAN: 12,
BENGALI: 13,
CAMBODIAN: 14,
CJK_EARTHLY_BRANCH: 15,
CJK_HEAVENLY_STEM: 16,
CJK_IDEOGRAPHIC: 17,
DEVANAGARI: 18,
ETHIOPIC_NUMERIC: 19,
GEORGIAN: 20,
GUJARATI: 21,
GURMUKHI: 22,
HEBREW: 22,
HIRAGANA: 23,
HIRAGANA_IROHA: 24,
JAPANESE_FORMAL: 25,
JAPANESE_INFORMAL: 26,
KANNADA: 27,
KATAKANA: 28,
KATAKANA_IROHA: 29,
KHMER: 30,
KOREAN_HANGUL_FORMAL: 31,
KOREAN_HANJA_FORMAL: 32,
KOREAN_HANJA_INFORMAL: 33,
LAO: 34,
LOWER_ARMENIAN: 35,
MALAYALAM: 36,
MONGOLIAN: 37,
MYANMAR: 38,
ORIYA: 39,
PERSIAN: 40,
SIMP_CHINESE_FORMAL: 41,
SIMP_CHINESE_INFORMAL: 42,
TAMIL: 43,
TELUGU: 44,
THAI: 45,
TIBETAN: 46,
TRAD_CHINESE_FORMAL: 47,
TRAD_CHINESE_INFORMAL: 48,
UPPER_ARMENIAN: 49,
DISCLOSURE_OPEN: 50,
DISCLOSURE_CLOSED: 51
};
export type ListStylePosition = $Values<typeof LIST_STYLE_POSITION>;
export type ListStyleType = $Values<typeof LIST_STYLE_TYPE>;
const parseListStyleType = (type: string): ListStyleType => {
switch (type) {
case 'disc':
return LIST_STYLE_TYPE.DISC;
case 'circle':
return LIST_STYLE_TYPE.CIRCLE;
case 'square':
return LIST_STYLE_TYPE.SQUARE;
case 'decimal':
return LIST_STYLE_TYPE.DECIMAL;
case 'cjk-decimal':
return LIST_STYLE_TYPE.CJK_DECIMAL;
case 'decimal-leading-zero':
return LIST_STYLE_TYPE.DECIMAL_LEADING_ZERO;
case 'lower-roman':
return LIST_STYLE_TYPE.LOWER_ROMAN;
case 'upper-roman':
return LIST_STYLE_TYPE.UPPER_ROMAN;
case 'lower-greek':
return LIST_STYLE_TYPE.LOWER_GREEK;
case 'lower-alpha':
return LIST_STYLE_TYPE.LOWER_ALPHA;
case 'upper-alpha':
return LIST_STYLE_TYPE.UPPER_ALPHA;
case 'arabic-indic':
return LIST_STYLE_TYPE.ARABIC_INDIC;
case 'armenian':
return LIST_STYLE_TYPE.ARMENIAN;
case 'bengali':
return LIST_STYLE_TYPE.BENGALI;
case 'cambodian':
return LIST_STYLE_TYPE.CAMBODIAN;
case 'cjk-earthly-branch':
return LIST_STYLE_TYPE.CJK_EARTHLY_BRANCH;
case 'cjk-heavenly-stem':
return LIST_STYLE_TYPE.CJK_HEAVENLY_STEM;
case 'cjk-ideographic':
return LIST_STYLE_TYPE.CJK_IDEOGRAPHIC;
case 'devanagari':
return LIST_STYLE_TYPE.DEVANAGARI;
case 'ethiopic-numeric':
return LIST_STYLE_TYPE.ETHIOPIC_NUMERIC;
case 'georgian':
return LIST_STYLE_TYPE.GEORGIAN;
case 'gujarati':
return LIST_STYLE_TYPE.GUJARATI;
case 'gurmukhi':
return LIST_STYLE_TYPE.GURMUKHI;
case 'hebrew':
return LIST_STYLE_TYPE.HEBREW;
case 'hiragana':
return LIST_STYLE_TYPE.HIRAGANA;
case 'hiragana-iroha':
return LIST_STYLE_TYPE.HIRAGANA_IROHA;
case 'japanese-formal':
return LIST_STYLE_TYPE.JAPANESE_FORMAL;
case 'japanese-informal':
return LIST_STYLE_TYPE.JAPANESE_INFORMAL;
case 'kannada':
return LIST_STYLE_TYPE.KANNADA;
case 'katakana':
return LIST_STYLE_TYPE.KATAKANA;
case 'katakana-iroha':
return LIST_STYLE_TYPE.KATAKANA_IROHA;
case 'khmer':
return LIST_STYLE_TYPE.KHMER;
case 'korean-hangul-formal':
return LIST_STYLE_TYPE.KOREAN_HANGUL_FORMAL;
case 'korean-hanja-formal':
return LIST_STYLE_TYPE.KOREAN_HANJA_FORMAL;
case 'korean-hanja-informal':
return LIST_STYLE_TYPE.KOREAN_HANJA_INFORMAL;
case 'lao':
return LIST_STYLE_TYPE.LAO;
case 'lower-armenian':
return LIST_STYLE_TYPE.LOWER_ARMENIAN;
case 'malayalam':
return LIST_STYLE_TYPE.MALAYALAM;
case 'mongolian':
return LIST_STYLE_TYPE.MONGOLIAN;
case 'myanmar':
return LIST_STYLE_TYPE.MYANMAR;
case 'oriya':
return LIST_STYLE_TYPE.ORIYA;
case 'persian':
return LIST_STYLE_TYPE.PERSIAN;
case 'simp-chinese-formal':
return LIST_STYLE_TYPE.SIMP_CHINESE_FORMAL;
case 'simp-chinese-informal':
return LIST_STYLE_TYPE.SIMP_CHINESE_INFORMAL;
case 'tamil':
return LIST_STYLE_TYPE.TAMIL;
case 'telugu':
return LIST_STYLE_TYPE.TELUGU;
case 'thai':
return LIST_STYLE_TYPE.THAI;
case 'tibetan':
return LIST_STYLE_TYPE.TIBETAN;
case 'trad-chinese-formal':
return LIST_STYLE_TYPE.TRAD_CHINESE_FORMAL;
case 'trad-chinese-informal':
return LIST_STYLE_TYPE.TRAD_CHINESE_INFORMAL;
case 'upper-armenian':
return LIST_STYLE_TYPE.UPPER_ARMENIAN;
case 'disclosure-open':
return LIST_STYLE_TYPE.DISCLOSURE_OPEN;
case 'disclosure-closed':
return LIST_STYLE_TYPE.DISCLOSURE_CLOSED;
case 'none':
default:
return LIST_STYLE_TYPE.NONE;
}
};
export const parseListStyle = (style: CSSStyleDeclaration): ListStyle => {
const listStyleImage = parseBackgroundImage(style.getPropertyValue('list-style-image'));
return {
listStyleType: parseListStyleType(style.getPropertyValue('list-style-type')),
listStyleImage: listStyleImage.length ? listStyleImage[0] : null,
listStylePosition: parseListStylePosition(style.getPropertyValue('list-style-position'))
};
};
const parseListStylePosition = (position: string): ListStylePosition => {
switch (position) {
case 'inside':
return LIST_STYLE_POSITION.INSIDE;
case 'outside':
default:
return LIST_STYLE_POSITION.OUTSIDE;
}
};

11
src/parsing/margin.js Normal file
View File

@ -0,0 +1,11 @@
/* @flow */
'use strict';
import Length from '../Length';
const SIDES = ['top', 'right', 'bottom', 'left'];
export type Margin = Array<Length>;
export const parseMargin = (style: CSSStyleDeclaration): Margin => {
return SIDES.map(side => new Length(style.getPropertyValue(`margin-${side}`)));
};

View File

@ -250,7 +250,7 @@ export default class CanvasRenderer implements RenderTarget<HTMLCanvasElement> {
const {baseline} = this.options.fontMetrics.getMetrics(font);
this.rectangle(
text.bounds.left,
Math.round(text.bounds.top + baseline),
Math.round(text.bounds.top + text.bounds.height - baseline),
text.bounds.width,
1,
textDecorationColor

View File

@ -0,0 +1,102 @@
<!doctype html>
<html>
<head>
<title>List tests</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script type="text/javascript" src="../../test.js"></script>
<style>
li {
margin: 10px 5%;
}
.list1 {
list-style-type: circle;
}
.list2 {
list-style-image: url(../../assets/image.jpg);
}
.list3 {
list-style-image: linear-gradient(60deg, deeppink, aquamarine);
list-style-position: inside;
}
.list4 {
}
.list5 {
list-style-type: lower-roman;
}
.list6 {
list-style-type: hiragana;
}
.list7 {
list-style-type: simp-chinese-informal;
}
.list8 {
list-style-type: lower-roman;
}
.list8 li {
display: block;
}
.list9 {
display: list-item;
list-style-type: lower-alpha;
margin: 10px;
position: relative;
left: 200px;
}
</style>
</head>
<body>
<ul class="list1">
<li>Alpha</li>
<li>Beta</li>
<li>Gamma</li>
</ul>
<ul class="list2">
<li>Alpha</li>
<li>Beta</li>
<li>Gamma</li>
</ul>
<ul class="list3">
<li>Alpha</li>
<li>Beta</li>
<li>Gamma</li>
</ul>
<ol class="list4" start="-1">
<li>Alpha</li>
<li>Beta</li>
<li>Gamma</li>
</ol>
<ol class="list5">
<li>Alpha</li>
<li value="5">Beta</li>
<li>Gamma</li>
</ol>
<ol class="list6">
<li>Alpha</li>
<li>Beta</li>
<li>Gamma</li>
</ol>
<ol class="list7">
<li>Alpha</li>
<li>Beta</li>
<li>Gamma</li>
</ol>
<ol class="list8">
<li>Alpha</li>
<li>Beta</li>
<li>Gamma</li>
</ol>
<div class="list9">Alpha</div>
<div class="list9">Beta</div>
<div class="list9">Gamma</div>
</body>
</html>

View File

@ -1,84 +1,122 @@
<!DOCTYPE html>
<html>
<head>
<title>Overflow tests</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script type="text/javascript" src="../../test.js"></script>
<style>
.small{
font-size:14px;
}
<head>
<title>Overflow tests</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script type="text/javascript" src="../../test.js"></script>
<style>
.small{
font-size:14px;
}
.medium{
font-size:18px;
}
.large{
font-size:24px;
}
div{
background: #ccc;
border:6px solid black;
width:300px;
height:200px;
margin: 0 0 60px 0;
}
h1 {
margin:0;
line-height: 40px;
}
body {
font-family: Arial;
line-height: 17px;
font-size: 1em;
}
</style>
.medium{
font-size:18px;
}
.large{
font-size:24px;
}
div{
background: #ccc;
border:6px solid black;
width:300px;
height:200px;
margin: 0 0 60px 0;
}
h1 {
margin:0;
line-height: 40px;
}
body {
font-family: Arial;
line-height: 17px;
font-size: 1em;
}
.cell {
display: inline-block;
width: 100px;
float: right;
height: 100px;
}
</head>
<body>
.cell p {
height: 60px;
}
<h1>Overflow: visible</h1>
<div> Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like <b>Aldus PageMaker</b> including versions of Lorem Ipsum.
</div>
.visible {
overflow: visible;
}
.hidden {
overflow: hidden;
}
.scroll {
overflow: scroll;
}
.auto {
overflow: auto;
}
</style>
<h1>Overflow: hidden</h1>
<div style="overflow:hidden;float: right; height: 100px; border-radius: 100%; border-color: transparent;">
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s
</head>
<body>
<div class="cell">
visible
<p class="visible">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec luctus pretium facilisis. Praesent rutrum eget nisl in tristique. Sed tincidunt nisl et tellus vulputate, nec rhoncus orci pretium.</p>
</div>
<div class="cell">
hidden
<p class="hidden">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec luctus pretium facilisis. Praesent rutrum eget nisl in tristique. Sed tincidunt nisl et tellus vulputate, nec rhoncus orci pretium.</p>
</div>
<div class="cell">
scroll
<p class="scroll">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec luctus pretium facilisis. Praesent rutrum eget nisl in tristique. Sed tincidunt nisl et tellus vulputate, nec rhoncus orci pretium.</p>
</div>
<div class="cell">
auto
<p class="auto">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec luctus pretium facilisis. Praesent rutrum eget nisl in tristique. Sed tincidunt nisl et tellus vulputate, nec rhoncus orci pretium.</p>
</div>
<h1>Overflow: visible</h1>
<div> Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like <b>Aldus PageMaker</b> including versions of Lorem Ipsum.
</div>
with the release of <div style="border-width:10px 0 5px 0;background:green;">a</div>Letraset sheets containing Lorem Ipsum passages, <img src="../../assets/image.jpg" /> and more recently with desktop publishing software like <b>Aldus PageMaker</b> including versions of Lorem Ipsum.
<h1>Overflow: hidden</h1>
<div style="overflow:hidden;float: right; height: 100px; border-radius: 100%; border-color: transparent;">
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s
with the release of <div style="border-width:10px 0 5px 0;background:green;">a</div>Letraset sheets containing Lorem Ipsum passages, <img src="../../assets/image.jpg" /> and more recently with desktop publishing software like <b>Aldus PageMaker</b> including versions of Lorem Ipsum.
<div style="overflow:visible;position:relative;"><u>position:relative within a overflow:hidden element</u><br /> <br />
<div style="overflow:visible;position:relative;"><u>position:relative within a overflow:hidden element</u><br /> <br />
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like <b>Aldus PageMaker</b> including versions of Lorem Ipsum.
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like <b>Aldus PageMaker</b> including versions of Lorem Ipsum.
</div>
</div>
<div style="overflow:hidden;">
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s
</div>
</div>
<div style="overflow:hidden;">
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s
with the release of <div style="border-width:10px 0 5px 0;background:green;">a</div>Letraset sheets containing Lorem Ipsum passages, <img src="../../assets/image.jpg" /> and more recently with desktop publishing software like <b>Aldus PageMaker</b> including versions of Lorem Ipsum.
with the release of <div style="border-width:10px 0 5px 0;background:green;">a</div>Letraset sheets containing Lorem Ipsum passages, <img src="../../assets/image.jpg" /> and more recently with desktop publishing software like <b>Aldus PageMaker</b> including versions of Lorem Ipsum.
<div style="overflow:visible;position:relative;"><u>position:relative within a overflow:hidden element</u><br /> <br />
<div style="overflow:visible;position:relative;"><u>position:relative within a overflow:hidden element</u><br /> <br />
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like <b>Aldus PageMaker</b> including versions of Lorem Ipsum.
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like <b>Aldus PageMaker</b> including versions of Lorem Ipsum.
</div>
</div>
<div style="overflow:scroll; height: 100px;" id="scroll">
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s
</div>
</div>
<div style="overflow:scroll; height: 100px;" id="scroll">
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s
with the release of <div style="border-width:10px 0 5px 0;background:green;">a</div>Letraset sheets containing Lorem Ipsum passages, <img src="../../assets/image.jpg" /> and more recently with desktop publishing software like <b>Aldus PageMaker</b> including versions of Lorem Ipsum.
with the release of <div style="border-width:10px 0 5px 0;background:green;">a</div>Letraset sheets containing Lorem Ipsum passages, <img src="../../assets/image.jpg" /> and more recently with desktop publishing software like <b>Aldus PageMaker</b> including versions of Lorem Ipsum.
<div style="overflow:visible;position:relative;"><u>position:relative within a overflow:hidden element</u><br /> <br />
<div style="overflow:visible;position:relative;"><u>position:relative within a overflow:hidden element</u><br /> <br />
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like <b>Aldus PageMaker</b> including versions of Lorem Ipsum.
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like <b>Aldus PageMaker</b> including versions of Lorem Ipsum.
</div>
<script>
document.querySelector('#scroll').scrollTo(0, 200);
</script>
</div>
</body>
</div>
<script>
document.querySelector('#scroll').scrollTo(0, 200);
</script>
</div>
</body>
</html>

6
www/package-lock.json generated
View File

@ -4895,9 +4895,9 @@
"integrity": "sha1-nSLgyjKsyVs/RbjVtPb73AWv/VU="
},
"html2canvas": {
"version": "1.0.0-alpha.3",
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.0.0-alpha.3.tgz",
"integrity": "sha1-0QeAa+W1kuRr0iI7B3KTgaBIGzo=",
"version": "1.0.0-alpha.4",
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.0.0-alpha.4.tgz",
"integrity": "sha1-t1Q+6kUkAHitGEesk5CBpXUYCho=",
"requires": {
"punycode": "2.1.0"
}

View File

@ -18,7 +18,7 @@
"gatsby-source-filesystem": "^1.5.9",
"gatsby-transformer-remark": "^1.7.23",
"gzip-size": "^4.1.0",
"html2canvas": "1.0.0-alpha.3",
"html2canvas": "1.0.0-alpha.4",
"mkdirp": "^0.5.1",
"typography": "^0.16.6",
"typography-theme-github": "^0.15.10"

View File

@ -66,7 +66,9 @@ export default class Example extends Component {
position: 'fixed',
zIndex: '1000',
right: '-348.4px',
bottom: '-327.2px'
bottom: '-327.2px',
visibility: this.state.open ? 'visible' : 'hidden',
transition: 'visibility 0.3s cubic-bezier(0.42, 0, 0.58, 1)'
}}
>
<div
@ -107,6 +109,7 @@ export default class Example extends Component {
cursor: 'pointer',
transform: 'translate(-50%, -50%)',
borderRadius: '50%',
visibility: 'visible',
boxShadow:
'0 2px 2px 0 rgba(0,0,0,0.14), 0 1px 5px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.2)'
}}