📄 HTMLMesh.js
¶
📊 Analysis Summary¶
Metric | Count |
---|---|
🔧 Functions | 17 |
🧱 Classes | 2 |
📦 Imports | 7 |
📊 Variables & Constants | 38 |
📚 Table of Contents¶
🛠️ File Location:¶
📂 examples/jsm/interactive/HTMLMesh.js
📦 Imports¶
Name | Source |
---|---|
CanvasTexture |
three |
LinearFilter |
three |
Mesh |
three |
MeshBasicMaterial |
three |
PlaneGeometry |
three |
SRGBColorSpace |
three |
Color |
three |
Variables & Constants¶
Name | Type | Kind | Value | Exported |
---|---|---|---|---|
texture |
HTMLTexture |
let/var | new HTMLTexture( dom ) |
✗ |
geometry |
any |
let/var | new PlaneGeometry( texture.image.width * 0.001, texture.image.height * 0.001 ) |
✗ |
material |
any |
let/var | new MeshBasicMaterial( { map: texture, toneMapped: false, transparent: true } ) |
✗ |
observer |
MutationObserver |
let/var | new MutationObserver( () => { if ( ! this.scheduleUpdate ) { // ideally shoul... |
✗ |
config |
{ attributes: boolean; childList: boo... |
let/var | { attributes: true, childList: true, subtree: true, characterData: true } |
✗ |
canvases |
WeakMap<WeakKey, any> |
let/var | new WeakMap() |
✗ |
color |
any |
let/var | new Color() |
✗ |
clips |
any[] |
let/var | [] |
✗ |
isClipping |
boolean |
let/var | false |
✗ |
minX |
number |
let/var | - Infinity |
✗ |
minY |
number |
let/var | - Infinity |
✗ |
maxX |
number |
let/var | Infinity |
✗ |
maxY |
number |
let/var | Infinity |
✗ |
clip |
any |
let/var | clips[ i ] |
✗ |
borderWidth |
any |
let/var | style[ which + 'Width' ] |
✗ |
borderStyle |
any |
let/var | style[ which + 'Style' ] |
✗ |
borderColor |
any |
let/var | style[ which + 'Color' ] |
✗ |
x |
number |
let/var | 0 |
✗ |
y |
number |
let/var | 0 |
✗ |
width |
number |
let/var | 0 |
✗ |
height |
number |
let/var | 0 |
✗ |
dpr |
number |
let/var | window.devicePixelRatio |
✗ |
backgroundColor |
any |
let/var | style.backgroundColor |
✗ |
borders |
string[] |
let/var | [ 'borderTop', 'borderLeft', 'borderBottom', 'borderRight' ] |
✗ |
match |
boolean |
let/var | true |
✗ |
prevBorder |
any |
let/var | null |
✗ |
accentColor |
any |
let/var | style.accentColor |
✗ |
accentTextColor |
"white" \| "#111111" |
let/var | luminance < 0.5 ? 'white' : '#111111' |
✗ |
currentTextAlign |
any |
let/var | context.textAlign |
✗ |
properties |
{ color: string; fontFamily: any; fon... |
let/var | { color: accentTextColor, fontFamily: style.fontFamily, fontSize: height + 'p... |
✗ |
position |
number |
let/var | ( ( value - min ) / ( max - min ) ) * ( width - height ) |
✗ |
displayValue |
string |
let/var | element.type === 'password' ? '*'.repeat( element.value.length ) : element.value |
✗ |
isClipping |
boolean |
let/var | style.overflow === 'auto' \|\| style.overflow === 'hidden' |
✗ |
clipper |
any |
let/var | new Clipper( context ) |
✗ |
mouseEventInit |
{ clientX: any; clientY: any; view: a... |
let/var | { clientX: ( x * element.offsetWidth ) + element.offsetLeft, clientY: ( y * e... |
✗ |
width |
any |
let/var | rect.width |
✗ |
offsetX |
number |
let/var | x - rect.x |
✗ |
proportion |
number |
let/var | offsetX / width |
✗ |
Functions¶
onEvent(event: any): void
¶
Parameters:
event
any
Returns: void
Calls:
material.map.dispatchDOMEvent
HTMLTexture.dispatchDOMEvent(event: any): void
¶
Parameters:
event
any
Returns: void
Calls:
htmlevent
Code
HTMLTexture.update(): void
¶
Returns: void
Calls:
html2canvas
Code
HTMLTexture.dispose(): void
¶
Returns: void
Calls:
this.observer.disconnect
clearTimeout
super.dispose
Code
html2canvas(element: any): any
¶
Parameters:
element
any
Returns: any
Calls:
document.createRange
context.restore
Math.max
Math.min
context.save
context.beginPath
context.rect
context.clip
clips.push
doClip
clips.pop
string.toUpperCase
context.fillText
parseFloat
context.moveTo
context.arcTo
context.closePath
context.lineTo
context.stroke
range.selectNode
range.getBoundingClientRect
drawText
element.nodeValue.trim
element.getBoundingClientRect
context.scale
context.drawImage
window.getComputedStyle
buildRectPath
context.fill
drawBorder
color.set
Math.sqrt
[ 'min', 'max', 'value' ].map
clipper.add
'*'.repeat
parseInt
clipper.remove
drawElement
canvases.get
document.createElement
canvases.set
canvas.getContext
context.clearRect
Internal Comments:
// Do not render invisible elements, comments and scripts.
// text (x4)
// Canvas element (x2)
// Get the border of the element used for fill and border (x3)
// If all the borders match then stroke the round rectangle (x2)
// They all match so stroke the rectangle from before allows for border-radius (x2)
// Otherwise draw individual borders (x3)
/*
// debug
context.strokeStyle = '#' + Math.random().toString( 16 ).slice( - 3 );
context.strokeRect( x - 0.5, y - 0.5, width + 1, height + 1 );
*/ (x2)
// console.time( 'drawElement' ); (x4)
// console.timeEnd( 'drawElement' );
Code
function html2canvas( element ) {
const range = document.createRange();
const color = new Color();
function Clipper( context ) {
const clips = [];
let isClipping = false;
function doClip() {
if ( isClipping ) {
isClipping = false;
context.restore();
}
if ( clips.length === 0 ) return;
let minX = - Infinity, minY = - Infinity;
let maxX = Infinity, maxY = Infinity;
for ( let i = 0; i < clips.length; i ++ ) {
const clip = clips[ i ];
minX = Math.max( minX, clip.x );
minY = Math.max( minY, clip.y );
maxX = Math.min( maxX, clip.x + clip.width );
maxY = Math.min( maxY, clip.y + clip.height );
}
context.save();
context.beginPath();
context.rect( minX, minY, maxX - minX, maxY - minY );
context.clip();
isClipping = true;
}
return {
add: function ( clip ) {
clips.push( clip );
doClip();
},
remove: function () {
clips.pop();
doClip();
}
};
}
function drawText( style, x, y, string ) {
if ( string !== '' ) {
if ( style.textTransform === 'uppercase' ) {
string = string.toUpperCase();
}
context.font = style.fontWeight + ' ' + style.fontSize + ' ' + style.fontFamily;
context.textBaseline = 'top';
context.fillStyle = style.color;
context.fillText( string, x, y + parseFloat( style.fontSize ) * 0.1 );
}
}
function buildRectPath( x, y, w, h, r ) {
if ( w < 2 * r ) r = w / 2;
if ( h < 2 * r ) r = h / 2;
context.beginPath();
context.moveTo( x + r, y );
context.arcTo( x + w, y, x + w, y + h, r );
context.arcTo( x + w, y + h, x, y + h, r );
context.arcTo( x, y + h, x, y, r );
context.arcTo( x, y, x + w, y, r );
context.closePath();
}
function drawBorder( style, which, x, y, width, height ) {
const borderWidth = style[ which + 'Width' ];
const borderStyle = style[ which + 'Style' ];
const borderColor = style[ which + 'Color' ];
if ( borderWidth !== '0px' && borderStyle !== 'none' && borderColor !== 'transparent' && borderColor !== 'rgba(0, 0, 0, 0)' ) {
context.strokeStyle = borderColor;
context.lineWidth = parseFloat( borderWidth );
context.beginPath();
context.moveTo( x, y );
context.lineTo( x + width, y + height );
context.stroke();
}
}
function drawElement( element, style ) {
// Do not render invisible elements, comments and scripts.
if ( element.nodeType === Node.COMMENT_NODE || element.nodeName === 'SCRIPT' || ( element.style && element.style.display === 'none' ) ) {
return;
}
let x = 0, y = 0, width = 0, height = 0;
if ( element.nodeType === Node.TEXT_NODE ) {
// text
range.selectNode( element );
const rect = range.getBoundingClientRect();
x = rect.left - offset.left - 0.5;
y = rect.top - offset.top - 0.5;
width = rect.width;
height = rect.height;
drawText( style, x, y, element.nodeValue.trim() );
} else if ( element instanceof HTMLCanvasElement ) {
// Canvas element
const rect = element.getBoundingClientRect();
x = rect.left - offset.left - 0.5;
y = rect.top - offset.top - 0.5;
context.save();
const dpr = window.devicePixelRatio;
context.scale( 1 / dpr, 1 / dpr );
context.drawImage( element, x, y );
context.restore();
} else if ( element instanceof HTMLImageElement ) {
const rect = element.getBoundingClientRect();
x = rect.left - offset.left - 0.5;
y = rect.top - offset.top - 0.5;
width = rect.width;
height = rect.height;
context.drawImage( element, x, y, width, height );
} else {
const rect = element.getBoundingClientRect();
x = rect.left - offset.left - 0.5;
y = rect.top - offset.top - 0.5;
width = rect.width;
height = rect.height;
style = window.getComputedStyle( element );
// Get the border of the element used for fill and border
buildRectPath( x, y, width, height, parseFloat( style.borderRadius ) );
const backgroundColor = style.backgroundColor;
if ( backgroundColor !== 'transparent' && backgroundColor !== 'rgba(0, 0, 0, 0)' ) {
context.fillStyle = backgroundColor;
context.fill();
}
// If all the borders match then stroke the round rectangle
const borders = [ 'borderTop', 'borderLeft', 'borderBottom', 'borderRight' ];
let match = true;
let prevBorder = null;
for ( const border of borders ) {
if ( prevBorder !== null ) {
match = ( style[ border + 'Width' ] === style[ prevBorder + 'Width' ] ) &&
( style[ border + 'Color' ] === style[ prevBorder + 'Color' ] ) &&
( style[ border + 'Style' ] === style[ prevBorder + 'Style' ] );
}
if ( match === false ) break;
prevBorder = border;
}
if ( match === true ) {
// They all match so stroke the rectangle from before allows for border-radius
const width = parseFloat( style.borderTopWidth );
if ( style.borderTopWidth !== '0px' && style.borderTopStyle !== 'none' && style.borderTopColor !== 'transparent' && style.borderTopColor !== 'rgba(0, 0, 0, 0)' ) {
context.strokeStyle = style.borderTopColor;
context.lineWidth = width;
context.stroke();
}
} else {
// Otherwise draw individual borders
drawBorder( style, 'borderTop', x, y, width, 0 );
drawBorder( style, 'borderLeft', x, y, 0, height );
drawBorder( style, 'borderBottom', x, y + height, width, 0 );
drawBorder( style, 'borderRight', x + width, y, 0, height );
}
if ( element instanceof HTMLInputElement ) {
let accentColor = style.accentColor;
if ( accentColor === undefined || accentColor === 'auto' ) accentColor = style.color;
color.set( accentColor );
const luminance = Math.sqrt( 0.299 * ( color.r ** 2 ) + 0.587 * ( color.g ** 2 ) + 0.114 * ( color.b ** 2 ) );
const accentTextColor = luminance < 0.5 ? 'white' : '#111111';
if ( element.type === 'radio' ) {
buildRectPath( x, y, width, height, height );
context.fillStyle = 'white';
context.strokeStyle = accentColor;
context.lineWidth = 1;
context.fill();
context.stroke();
if ( element.checked ) {
buildRectPath( x + 2, y + 2, width - 4, height - 4, height );
context.fillStyle = accentColor;
context.strokeStyle = accentTextColor;
context.lineWidth = 2;
context.fill();
context.stroke();
}
}
if ( element.type === 'checkbox' ) {
buildRectPath( x, y, width, height, 2 );
context.fillStyle = element.checked ? accentColor : 'white';
context.strokeStyle = element.checked ? accentTextColor : accentColor;
context.lineWidth = 1;
context.stroke();
context.fill();
if ( element.checked ) {
const currentTextAlign = context.textAlign;
context.textAlign = 'center';
const properties = {
color: accentTextColor,
fontFamily: style.fontFamily,
fontSize: height + 'px',
fontWeight: 'bold'
};
drawText( properties, x + ( width / 2 ), y, '✔' );
context.textAlign = currentTextAlign;
}
}
if ( element.type === 'range' ) {
const [ min, max, value ] = [ 'min', 'max', 'value' ].map( property => parseFloat( element[ property ] ) );
const position = ( ( value - min ) / ( max - min ) ) * ( width - height );
buildRectPath( x, y + ( height / 4 ), width, height / 2, height / 4 );
context.fillStyle = accentTextColor;
context.strokeStyle = accentColor;
context.lineWidth = 1;
context.fill();
context.stroke();
buildRectPath( x, y + ( height / 4 ), position + ( height / 2 ), height / 2, height / 4 );
context.fillStyle = accentColor;
context.fill();
buildRectPath( x + position, y, height, height, height / 2 );
context.fillStyle = accentColor;
context.fill();
}
if ( element.type === 'color' || element.type === 'text' || element.type === 'number' || element.type === 'email' || element.type === 'password' ) {
clipper.add( { x: x, y: y, width: width, height: height } );
const displayValue = element.type === 'password' ? '*'.repeat( element.value.length ) : element.value;
drawText( style, x + parseInt( style.paddingLeft ), y + parseInt( style.paddingTop ), displayValue );
clipper.remove();
}
}
}
/*
// debug
context.strokeStyle = '#' + Math.random().toString( 16 ).slice( - 3 );
context.strokeRect( x - 0.5, y - 0.5, width + 1, height + 1 );
*/
const isClipping = style.overflow === 'auto' || style.overflow === 'hidden';
if ( isClipping ) clipper.add( { x: x, y: y, width: width, height: height } );
for ( let i = 0; i < element.childNodes.length; i ++ ) {
drawElement( element.childNodes[ i ], style );
}
if ( isClipping ) clipper.remove();
}
const offset = element.getBoundingClientRect();
let canvas = canvases.get( element );
if ( canvas === undefined ) {
canvas = document.createElement( 'canvas' );
canvas.width = offset.width;
canvas.height = offset.height;
canvases.set( element, canvas );
}
const context = canvas.getContext( '2d'/*, { alpha: false }*/ );
const clipper = new Clipper( context );
// console.time( 'drawElement' );
context.clearRect( 0, 0, canvas.width, canvas.height );
drawElement( element );
// console.timeEnd( 'drawElement' );
return canvas;
}
Clipper(context: any): { add: (clip: any) => void; remove: () => void; }
¶
Parameters:
context
any
Returns: { add: (clip: any) => void; remove: () => void; }
Calls:
context.restore
Math.max
Math.min
context.save
context.beginPath
context.rect
context.clip
clips.push
doClip
clips.pop
Code
function Clipper( context ) {
const clips = [];
let isClipping = false;
function doClip() {
if ( isClipping ) {
isClipping = false;
context.restore();
}
if ( clips.length === 0 ) return;
let minX = - Infinity, minY = - Infinity;
let maxX = Infinity, maxY = Infinity;
for ( let i = 0; i < clips.length; i ++ ) {
const clip = clips[ i ];
minX = Math.max( minX, clip.x );
minY = Math.max( minY, clip.y );
maxX = Math.min( maxX, clip.x + clip.width );
maxY = Math.min( maxY, clip.y + clip.height );
}
context.save();
context.beginPath();
context.rect( minX, minY, maxX - minX, maxY - minY );
context.clip();
isClipping = true;
}
return {
add: function ( clip ) {
clips.push( clip );
doClip();
},
remove: function () {
clips.pop();
doClip();
}
};
}
doClip(): void
¶
Returns: void
Calls:
context.restore
Math.max
Math.min
context.save
context.beginPath
context.rect
context.clip
Code
function doClip() {
if ( isClipping ) {
isClipping = false;
context.restore();
}
if ( clips.length === 0 ) return;
let minX = - Infinity, minY = - Infinity;
let maxX = Infinity, maxY = Infinity;
for ( let i = 0; i < clips.length; i ++ ) {
const clip = clips[ i ];
minX = Math.max( minX, clip.x );
minY = Math.max( minY, clip.y );
maxX = Math.min( maxX, clip.x + clip.width );
maxY = Math.min( maxY, clip.y + clip.height );
}
context.save();
context.beginPath();
context.rect( minX, minY, maxX - minX, maxY - minY );
context.clip();
isClipping = true;
}
add(clip: any): void
¶
Parameters:
clip
any
Returns: void
Calls:
clips.push
doClip
remove(): void
¶
Returns: void
Calls:
clips.pop
doClip
add(clip: any): void
¶
Parameters:
clip
any
Returns: void
Calls:
clips.push
doClip
remove(): void
¶
Returns: void
Calls:
clips.pop
doClip
drawText(style: any, x: any, y: any, string: any): void
¶
Parameters:
style
any
x
any
y
any
string
any
Returns: void
Calls:
string.toUpperCase
context.fillText
parseFloat
Code
function drawText( style, x, y, string ) {
if ( string !== '' ) {
if ( style.textTransform === 'uppercase' ) {
string = string.toUpperCase();
}
context.font = style.fontWeight + ' ' + style.fontSize + ' ' + style.fontFamily;
context.textBaseline = 'top';
context.fillStyle = style.color;
context.fillText( string, x, y + parseFloat( style.fontSize ) * 0.1 );
}
}
buildRectPath(x: any, y: any, w: any, h: any, r: any): void
¶
Parameters:
x
any
y
any
w
any
h
any
r
any
Returns: void
Calls:
context.beginPath
context.moveTo
context.arcTo
context.closePath
Code
function buildRectPath( x, y, w, h, r ) {
if ( w < 2 * r ) r = w / 2;
if ( h < 2 * r ) r = h / 2;
context.beginPath();
context.moveTo( x + r, y );
context.arcTo( x + w, y, x + w, y + h, r );
context.arcTo( x + w, y + h, x, y + h, r );
context.arcTo( x, y + h, x, y, r );
context.arcTo( x, y, x + w, y, r );
context.closePath();
}
drawBorder(style: any, which: any, x: any, y: any, width: any, height: any): void
¶
Parameters:
style
any
which
any
x
any
y
any
width
any
height
any
Returns: void
Calls:
parseFloat
context.beginPath
context.moveTo
context.lineTo
context.stroke
Code
function drawBorder( style, which, x, y, width, height ) {
const borderWidth = style[ which + 'Width' ];
const borderStyle = style[ which + 'Style' ];
const borderColor = style[ which + 'Color' ];
if ( borderWidth !== '0px' && borderStyle !== 'none' && borderColor !== 'transparent' && borderColor !== 'rgba(0, 0, 0, 0)' ) {
context.strokeStyle = borderColor;
context.lineWidth = parseFloat( borderWidth );
context.beginPath();
context.moveTo( x, y );
context.lineTo( x + width, y + height );
context.stroke();
}
}
drawElement(element: any, style: any): void
¶
Parameters:
element
any
style
any
Returns: void
Calls:
range.selectNode
range.getBoundingClientRect
drawText
element.nodeValue.trim
element.getBoundingClientRect
context.save
context.scale
context.drawImage
context.restore
window.getComputedStyle
buildRectPath
parseFloat
context.fill
context.stroke
drawBorder
color.set
Math.sqrt
[ 'min', 'max', 'value' ].map
clipper.add
'*'.repeat
parseInt
clipper.remove
drawElement
Internal Comments:
// Do not render invisible elements, comments and scripts.
// text (x4)
// Canvas element (x2)
// Get the border of the element used for fill and border (x3)
// If all the borders match then stroke the round rectangle (x2)
// They all match so stroke the rectangle from before allows for border-radius (x2)
// Otherwise draw individual borders (x3)
/*
// debug
context.strokeStyle = '#' + Math.random().toString( 16 ).slice( - 3 );
context.strokeRect( x - 0.5, y - 0.5, width + 1, height + 1 );
*/ (x2)
Code
function drawElement( element, style ) {
// Do not render invisible elements, comments and scripts.
if ( element.nodeType === Node.COMMENT_NODE || element.nodeName === 'SCRIPT' || ( element.style && element.style.display === 'none' ) ) {
return;
}
let x = 0, y = 0, width = 0, height = 0;
if ( element.nodeType === Node.TEXT_NODE ) {
// text
range.selectNode( element );
const rect = range.getBoundingClientRect();
x = rect.left - offset.left - 0.5;
y = rect.top - offset.top - 0.5;
width = rect.width;
height = rect.height;
drawText( style, x, y, element.nodeValue.trim() );
} else if ( element instanceof HTMLCanvasElement ) {
// Canvas element
const rect = element.getBoundingClientRect();
x = rect.left - offset.left - 0.5;
y = rect.top - offset.top - 0.5;
context.save();
const dpr = window.devicePixelRatio;
context.scale( 1 / dpr, 1 / dpr );
context.drawImage( element, x, y );
context.restore();
} else if ( element instanceof HTMLImageElement ) {
const rect = element.getBoundingClientRect();
x = rect.left - offset.left - 0.5;
y = rect.top - offset.top - 0.5;
width = rect.width;
height = rect.height;
context.drawImage( element, x, y, width, height );
} else {
const rect = element.getBoundingClientRect();
x = rect.left - offset.left - 0.5;
y = rect.top - offset.top - 0.5;
width = rect.width;
height = rect.height;
style = window.getComputedStyle( element );
// Get the border of the element used for fill and border
buildRectPath( x, y, width, height, parseFloat( style.borderRadius ) );
const backgroundColor = style.backgroundColor;
if ( backgroundColor !== 'transparent' && backgroundColor !== 'rgba(0, 0, 0, 0)' ) {
context.fillStyle = backgroundColor;
context.fill();
}
// If all the borders match then stroke the round rectangle
const borders = [ 'borderTop', 'borderLeft', 'borderBottom', 'borderRight' ];
let match = true;
let prevBorder = null;
for ( const border of borders ) {
if ( prevBorder !== null ) {
match = ( style[ border + 'Width' ] === style[ prevBorder + 'Width' ] ) &&
( style[ border + 'Color' ] === style[ prevBorder + 'Color' ] ) &&
( style[ border + 'Style' ] === style[ prevBorder + 'Style' ] );
}
if ( match === false ) break;
prevBorder = border;
}
if ( match === true ) {
// They all match so stroke the rectangle from before allows for border-radius
const width = parseFloat( style.borderTopWidth );
if ( style.borderTopWidth !== '0px' && style.borderTopStyle !== 'none' && style.borderTopColor !== 'transparent' && style.borderTopColor !== 'rgba(0, 0, 0, 0)' ) {
context.strokeStyle = style.borderTopColor;
context.lineWidth = width;
context.stroke();
}
} else {
// Otherwise draw individual borders
drawBorder( style, 'borderTop', x, y, width, 0 );
drawBorder( style, 'borderLeft', x, y, 0, height );
drawBorder( style, 'borderBottom', x, y + height, width, 0 );
drawBorder( style, 'borderRight', x + width, y, 0, height );
}
if ( element instanceof HTMLInputElement ) {
let accentColor = style.accentColor;
if ( accentColor === undefined || accentColor === 'auto' ) accentColor = style.color;
color.set( accentColor );
const luminance = Math.sqrt( 0.299 * ( color.r ** 2 ) + 0.587 * ( color.g ** 2 ) + 0.114 * ( color.b ** 2 ) );
const accentTextColor = luminance < 0.5 ? 'white' : '#111111';
if ( element.type === 'radio' ) {
buildRectPath( x, y, width, height, height );
context.fillStyle = 'white';
context.strokeStyle = accentColor;
context.lineWidth = 1;
context.fill();
context.stroke();
if ( element.checked ) {
buildRectPath( x + 2, y + 2, width - 4, height - 4, height );
context.fillStyle = accentColor;
context.strokeStyle = accentTextColor;
context.lineWidth = 2;
context.fill();
context.stroke();
}
}
if ( element.type === 'checkbox' ) {
buildRectPath( x, y, width, height, 2 );
context.fillStyle = element.checked ? accentColor : 'white';
context.strokeStyle = element.checked ? accentTextColor : accentColor;
context.lineWidth = 1;
context.stroke();
context.fill();
if ( element.checked ) {
const currentTextAlign = context.textAlign;
context.textAlign = 'center';
const properties = {
color: accentTextColor,
fontFamily: style.fontFamily,
fontSize: height + 'px',
fontWeight: 'bold'
};
drawText( properties, x + ( width / 2 ), y, '✔' );
context.textAlign = currentTextAlign;
}
}
if ( element.type === 'range' ) {
const [ min, max, value ] = [ 'min', 'max', 'value' ].map( property => parseFloat( element[ property ] ) );
const position = ( ( value - min ) / ( max - min ) ) * ( width - height );
buildRectPath( x, y + ( height / 4 ), width, height / 2, height / 4 );
context.fillStyle = accentTextColor;
context.strokeStyle = accentColor;
context.lineWidth = 1;
context.fill();
context.stroke();
buildRectPath( x, y + ( height / 4 ), position + ( height / 2 ), height / 2, height / 4 );
context.fillStyle = accentColor;
context.fill();
buildRectPath( x + position, y, height, height, height / 2 );
context.fillStyle = accentColor;
context.fill();
}
if ( element.type === 'color' || element.type === 'text' || element.type === 'number' || element.type === 'email' || element.type === 'password' ) {
clipper.add( { x: x, y: y, width: width, height: height } );
const displayValue = element.type === 'password' ? '*'.repeat( element.value.length ) : element.value;
drawText( style, x + parseInt( style.paddingLeft ), y + parseInt( style.paddingTop ), displayValue );
clipper.remove();
}
}
}
/*
// debug
context.strokeStyle = '#' + Math.random().toString( 16 ).slice( - 3 );
context.strokeRect( x - 0.5, y - 0.5, width + 1, height + 1 );
*/
const isClipping = style.overflow === 'auto' || style.overflow === 'hidden';
if ( isClipping ) clipper.add( { x: x, y: y, width: width, height: height } );
for ( let i = 0; i < element.childNodes.length; i ++ ) {
drawElement( element.childNodes[ i ], style );
}
if ( isClipping ) clipper.remove();
}
htmlevent(element: any, event: any, x: any, y: any): void
¶
Parameters:
element
any
event
any
x
any
y
any
Returns: void
Calls:
window.dispatchEvent
element.getBoundingClientRect
element.dispatchEvent
[ 'min', 'max' ].map
parseFloat
element.focus
traverse
Code
function htmlevent( element, event, x, y ) {
const mouseEventInit = {
clientX: ( x * element.offsetWidth ) + element.offsetLeft,
clientY: ( y * element.offsetHeight ) + element.offsetTop,
view: element.ownerDocument.defaultView
};
window.dispatchEvent( new MouseEvent( event, mouseEventInit ) );
const rect = element.getBoundingClientRect();
x = x * rect.width + rect.left;
y = y * rect.height + rect.top;
function traverse( element ) {
if ( element.nodeType !== Node.TEXT_NODE && element.nodeType !== Node.COMMENT_NODE ) {
const rect = element.getBoundingClientRect();
if ( x > rect.left && x < rect.right && y > rect.top && y < rect.bottom ) {
element.dispatchEvent( new MouseEvent( event, mouseEventInit ) );
if ( element instanceof HTMLInputElement && element.type === 'range' && ( event === 'mousedown' || event === 'click' ) ) {
const [ min, max ] = [ 'min', 'max' ].map( property => parseFloat( element[ property ] ) );
const width = rect.width;
const offsetX = x - rect.x;
const proportion = offsetX / width;
element.value = min + ( max - min ) * proportion;
element.dispatchEvent( new InputEvent( 'input', { bubbles: true } ) );
}
if ( element instanceof HTMLInputElement && ( element.type === 'text' || element.type === 'number' || element.type === 'email' || element.type === 'password' ) && ( event === 'mousedown' || event === 'click' ) ) {
element.focus();
}
}
for ( let i = 0; i < element.childNodes.length; i ++ ) {
traverse( element.childNodes[ i ] );
}
}
}
traverse( element );
}
traverse(element: any): void
¶
Parameters:
element
any
Returns: void
Calls:
element.getBoundingClientRect
element.dispatchEvent
[ 'min', 'max' ].map
parseFloat
element.focus
traverse
Code
function traverse( element ) {
if ( element.nodeType !== Node.TEXT_NODE && element.nodeType !== Node.COMMENT_NODE ) {
const rect = element.getBoundingClientRect();
if ( x > rect.left && x < rect.right && y > rect.top && y < rect.bottom ) {
element.dispatchEvent( new MouseEvent( event, mouseEventInit ) );
if ( element instanceof HTMLInputElement && element.type === 'range' && ( event === 'mousedown' || event === 'click' ) ) {
const [ min, max ] = [ 'min', 'max' ].map( property => parseFloat( element[ property ] ) );
const width = rect.width;
const offsetX = x - rect.x;
const proportion = offsetX / width;
element.value = min + ( max - min ) * proportion;
element.dispatchEvent( new InputEvent( 'input', { bubbles: true } ) );
}
if ( element instanceof HTMLInputElement && ( element.type === 'text' || element.type === 'number' || element.type === 'email' || element.type === 'password' ) && ( event === 'mousedown' || event === 'click' ) ) {
element.focus();
}
}
for ( let i = 0; i < element.childNodes.length; i ++ ) {
traverse( element.childNodes[ i ] );
}
}
}
Classes¶
HTMLMesh
¶
Class Code
class HTMLMesh extends Mesh {
/**
* Constructs a new HTML mesh.
*
* @param {HTMLElement} dom - The DOM element to display as a plane mesh.
*/
constructor( dom ) {
const texture = new HTMLTexture( dom );
const geometry = new PlaneGeometry( texture.image.width * 0.001, texture.image.height * 0.001 );
const material = new MeshBasicMaterial( { map: texture, toneMapped: false, transparent: true } );
super( geometry, material );
function onEvent( event ) {
material.map.dispatchDOMEvent( event );
}
this.addEventListener( 'mousedown', onEvent );
this.addEventListener( 'mousemove', onEvent );
this.addEventListener( 'mouseup', onEvent );
this.addEventListener( 'click', onEvent );
/**
* Frees the GPU-related resources allocated by this instance and removes all event listeners.
* Call this method whenever this instance is no longer used in your app.
*/
this.dispose = function () {
geometry.dispose();
material.dispose();
material.map.dispose();
canvases.delete( dom );
this.removeEventListener( 'mousedown', onEvent );
this.removeEventListener( 'mousemove', onEvent );
this.removeEventListener( 'mouseup', onEvent );
this.removeEventListener( 'click', onEvent );
};
}
}
HTMLTexture
¶
Class Code
class HTMLTexture extends CanvasTexture {
constructor( dom ) {
super( html2canvas( dom ) );
this.dom = dom;
this.anisotropy = 16;
this.colorSpace = SRGBColorSpace;
this.minFilter = LinearFilter;
this.magFilter = LinearFilter;
this.generateMipmaps = false;
// Create an observer on the DOM, and run html2canvas update in the next loop
const observer = new MutationObserver( () => {
if ( ! this.scheduleUpdate ) {
// ideally should use xr.requestAnimationFrame, here setTimeout to avoid passing the renderer
this.scheduleUpdate = setTimeout( () => this.update(), 16 );
}
} );
const config = { attributes: true, childList: true, subtree: true, characterData: true };
observer.observe( dom, config );
this.observer = observer;
}
dispatchDOMEvent( event ) {
if ( event.data ) {
htmlevent( this.dom, event.type, event.data.x, event.data.y );
}
}
update() {
this.image = html2canvas( this.dom );
this.needsUpdate = true;
this.scheduleUpdate = null;
}
dispose() {
if ( this.observer ) {
this.observer.disconnect();
}
this.scheduleUpdate = clearTimeout( this.scheduleUpdate );
super.dispose();
}
}