Frontend Forever App
We have a mobile app for you to download and use. And you can unlock many features in the app.
Get it now
Intall Later
Run
HTML
CSS
Javascript
Output
Document
@charset "UTF-8"; @import url(https://fonts.googleapis.com/css?family=Nunito+Sans:300,400,600,700,800); *, :after, :before { box-sizing: border-box; padding: 0; margin: 0; } /* global */ *, *::before, *::after { margin: 0; padding: 0; border: 0; box-sizing: border-box; } *:focus { outline: none; } body { height: 100vh; overflow: hidden; } canvas { touch-action: none; }
console.log("Event Fired") //--- 'use strict'; //--- console.clear(); //--- let w = 0; let h = 0; let animationFrame = null; let isTouchDevice = false; const canvas = document.createElement( 'canvas' ); const context = canvas.getContext( '2d', { willReadFrequently: false, alpha: false } ); let imageData = null; let data = null; const center = { x: w / 2, y: h / 2 }; let pointerPos = { x: center.x, y: center.y }; let pointerPosSave = { x: pointerPos.x, y: pointerPos.y }; let pointerDownButton = -1; //--- const FLAME_COLORS = [ 255, 255, 255, 230, 230, 250, 255, 255, 0, 255, 215, 0, 255, 105, 180, 255, 99, 71, 72, 61, 139, 34, 34, 34, 26, 26, 26, 17, 17, 17, ]; let flameColorDepth = 39; let flameColorTable = createGradient( FLAME_COLORS, flameColorDepth ); let flameColorTableLength = Math.round( flameColorTable.length / 3 ) - 1; let gridWidth = 0; let gridHeight = 0; let windStrength = 0; let windDirection = 0; let pixelSize = 2; let pixelSizeSquared = pixelSize * pixelSize; let pixelHolderLength = 0; let pixelHolder = null; let pixelBufferHolder = null; let pixelImageBufferHolder = null; let pixelSpreadFrom = null; let pixelSpreadFromLengthPreCalc = 0; const RANDOM_TABLE_SIZE = 512; // Power of 2 const RANDOM_TABLE_MASK = RANDOM_TABLE_SIZE - 1; const randomTable = new Float32Array( RANDOM_TABLE_SIZE ); let randomIndex = 0; let gui = null; let stats = null; const ROCKET_PROPS = 4; const ROCKET_COUNT_MIN = 3; const ROCKET_COUNT_MAX = 25; const ROCKETS_PER_PIXEL = 1 / 300; let rocketCount = 0; let rocketHolderLength = 0; let rocketHolder = null; const IMAGE_THRESHOLD = 1; const IMAGE_MARGIN = 15; //--- function init() { isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0; //--- if ( isTouchDevice === true ) { canvas.addEventListener( 'touchmove', cursorMoveHandler, false ); canvas.addEventListener( 'touchstart', cursorEnterHandler, false ); canvas.addEventListener( 'touchend', cursorLeaveHandler, false ); canvas.addEventListener( 'touchcancel ', cursorLeaveHandler, false ); } else { canvas.addEventListener( 'pointermove', cursorMoveHandler, false ); canvas.addEventListener( 'pointerdown', cursorDownHandler, false ); canvas.addEventListener( 'pointerup', cursorUpHandler, false ); canvas.addEventListener( 'pointerenter', cursorEnterHandler, false ); canvas.addEventListener( 'pointerleave', cursorLeaveHandler, false ); } //--- for ( let i = 0; i < RANDOM_TABLE_SIZE; i++ ) { randomTable[ i ] = Math.random(); } //--- stats = new Stats(); stats.domElement.style.position = 'absolute'; document.body.appendChild( stats.domElement ); //--- document.body.appendChild( canvas ); window.addEventListener( 'resize', onResize, false ); restart( true ); } function onResize( event ) { restart(); } function restart( explosion = false ) { const innerWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; const innerHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight; //--- w = innerWidth; h = innerHeight; //--- canvas.width = w; canvas.height = h; imageData = context.getImageData( 0, 0, w, h ); data = imageData.data; //--- center.x = w / 2; center.y = h / 2; pointerPos.x = center.x; pointerPos.y = center.y; pointerPosSave.x = pointerPos.x / pixelSize | 0; pointerPosSave.y = pointerPos.y / pixelSize | 0; //--- gridWidth = Math.floor( w / pixelSize ); gridHeight = Math.floor( h / pixelSize ); //--- setBackground(); initFire( explosion ); //--- initRockets(); //--- const img = new Image(); img.src = imageSrc; img.onload = ( event ) => { initImageBuffer( event.target, pixelImageBufferHolder ); } //--- if ( animationFrame != null ) { cancelAnimFrame( animationFrame ); } render(); } //--- function initGUI() { const updatePixelsize = () => { pixelSize = guiSetting[ 'Pixelsize' ]; pixelSizeSquared = pixelSize * pixelSize; restart( false ); }; const updateWindStrength = () => { windStrength = guiSetting[ 'Wind strength' ]; spreadFrom(); }; const updateWindDirection = () => { windDirection = guiSetting[ 'Wind direction' ]; spreadFrom(); }; const updateFlameColorDepth = () => { flameColorDepth = guiSetting[ 'Color depth' ]; flameColorTable = createGradient( FLAME_COLORS, flameColorDepth ); flameColorTableLength = Math.round( flameColorTable.length / 3 ) - 1; setBackground(); }; const updateColorGradient = ( colorIndex ) => { const newColorArray = guiSetting[ `Color ${colorIndex + 1}` ]; FLAME_COLORS[ colorIndex * 3 ] = Math.round( newColorArray[ 0 ] ); FLAME_COLORS[ colorIndex * 3 + 1 ] = Math.round( newColorArray[ 1 ] ); FLAME_COLORS[ colorIndex * 3 + 2 ] = Math.round( newColorArray[ 2 ] ); flameColorTable = createGradient( FLAME_COLORS, flameColorDepth ); setBackground(); }; const linkTo = () => { window.open( 'https://x.com/niklaswebdev', '_parent' ); }; const guiSetting = { 'Pixelsize': pixelSize, 'Wind strength': windStrength, 'Wind direction': windDirection, 'Color depth': flameColorDepth, 'Color 1': [ FLAME_COLORS[ 0 ], FLAME_COLORS[ 1 ], FLAME_COLORS[ 2 ] ], 'Color 2': [ FLAME_COLORS[ 3 ], FLAME_COLORS[ 4 ], FLAME_COLORS[ 5 ] ], 'Color 3': [ FLAME_COLORS[ 6 ], FLAME_COLORS[ 7 ], FLAME_COLORS[ 8 ] ], 'Color 4': [ FLAME_COLORS[ 9 ], FLAME_COLORS[ 10 ], FLAME_COLORS[ 11 ] ], 'Color 5': [ FLAME_COLORS[ 12 ], FLAME_COLORS[ 13 ], FLAME_COLORS[ 14 ] ], 'Color 6': [ FLAME_COLORS[ 15 ], FLAME_COLORS[ 16 ], FLAME_COLORS[ 17 ] ], 'Color 7': [ FLAME_COLORS[ 18 ], FLAME_COLORS[ 19 ], FLAME_COLORS[ 20 ] ], 'Color 8': [ FLAME_COLORS[ 21 ], FLAME_COLORS[ 22 ], FLAME_COLORS[ 23 ] ], 'Color 9': [ FLAME_COLORS[ 24 ], FLAME_COLORS[ 25 ], FLAME_COLORS[ 26 ] ], 'Color 10': [ FLAME_COLORS[ 27 ], FLAME_COLORS[ 28 ], FLAME_COLORS[ 29 ] ], '@niklaswebdev': linkTo }; gui = new dat.GUI(); gui.add( guiSetting, 'Pixelsize' ).min( 1 ).max( 10 ).step( 1 ).onChange( updatePixelsize ); gui.add( guiSetting, 'Wind strength' ).min( 0 ).max( 50 ).step( 1 ).onChange( updateWindStrength ); gui.add( guiSetting, 'Wind direction' ).min( -1 ).max( 1 ).step( 1 ).onChange( updateWindDirection ); gui.add( guiSetting, 'Color depth' ).min( FLAME_COLORS.length ).max( FLAME_COLORS.length / 3 * 10 ).step( 1 ).onChange( updateFlameColorDepth ); const folderColorGradient = gui.addFolder( 'Color gradient' ); folderColorGradient.addColor( guiSetting, 'Color 1' ).onChange( updateColorGradient.bind( this, 0 ) ); folderColorGradient.addColor( guiSetting, 'Color 2' ).onChange( updateColorGradient.bind( this, 1 ) ); folderColorGradient.addColor( guiSetting, 'Color 3' ).onChange( updateColorGradient.bind( this, 2 ) ); folderColorGradient.addColor( guiSetting, 'Color 4' ).onChange( updateColorGradient.bind( this, 3 ) ); folderColorGradient.addColor( guiSetting, 'Color 5' ).onChange( updateColorGradient.bind( this, 4 ) ); folderColorGradient.addColor( guiSetting, 'Color 6' ).onChange( updateColorGradient.bind( this, 5 ) ); folderColorGradient.addColor( guiSetting, 'Color 7' ).onChange( updateColorGradient.bind( this, 6 ) ); folderColorGradient.addColor( guiSetting, 'Color 8' ).onChange( updateColorGradient.bind( this, 7 ) ); folderColorGradient.addColor( guiSetting, 'Color 9' ).onChange( updateColorGradient.bind( this, 8 ) ); folderColorGradient.addColor( guiSetting, 'Color 10' ).onChange( updateColorGradient.bind( this, 9 ) ); gui.add( guiSetting, '@niklaswebdev' ); gui.close(); } //--- function setBackground() { const fCTL = flameColorTable.length; for ( let i = 0, l = data.length; i < l; i += 4 ) { data[ i ] = flameColorTable[ fCTL - 3 ]; data[ i + 1 ] = flameColorTable[ fCTL - 2 ]; data[ i + 2 ] = flameColorTable[ fCTL - 1 ]; } } function initFire( explosion = false ) { pixelHolderLength = gridWidth * gridHeight; pixelHolder = new Uint8Array( pixelHolderLength ); pixelBufferHolder = new Uint8Array( pixelHolderLength ); pixelImageBufferHolder = new Uint8Array( pixelHolderLength ); spreadFrom(); for ( let y = 0; y < h; y++ ) { for ( let x = 0; x < w; x++ ) { const index = y * gridWidth + x; if ( y === gridHeight - 1 && explosion ) { pixelHolder[ index ] = 0; } else { pixelHolder[ index ] = flameColorTableLength; } pixelImageBufferHolder[ index ] = flameColorTableLength; } } } function initRockets() { rocketCount = Math.floor( w * ROCKETS_PER_PIXEL ); rocketCount = Math.max( ROCKET_COUNT_MIN, Math.min( rocketCount, ROCKET_COUNT_MAX ) ); rocketHolderLength = rocketCount * ROCKET_PROPS; rocketHolder = new Float32Array( rocketHolderLength ); for ( let i = 0; i < rocketHolderLength; i += ROCKET_PROPS ) { const x = Math.random() * w / pixelSize; const y = ( h + Math.random() * h ) / pixelSize; const vx = getRandomNumber( -1, 1 ); const vy = getRandomNumber( -6, -3 ); rocketHolder[ i ] = x; rocketHolder[ i + 1 ] = y; rocketHolder[ i + 2 ] = vx; rocketHolder[ i + 3 ] = vy; } } function initImageBuffer( image, imageBuffer ) { const availableWidth = gridWidth - IMAGE_MARGIN * 2; const availableHeight = gridHeight - IMAGE_MARGIN * 2; let scaledWidth = Math.floor( image.width / pixelSize ); let scaledHeight = Math.floor( image.height / pixelSize ); const widthRatio = availableWidth / scaledWidth; const heightRatio = availableHeight / scaledHeight; const scale = Math.min( widthRatio, heightRatio, 1 ); scaledWidth = Math.floor( scaledWidth * scale ); scaledHeight = Math.floor( scaledHeight * scale ); //--- const startX = Math.floor( ( gridWidth - scaledWidth ) / 2 ); const startY = Math.floor( ( gridHeight - scaledHeight ) / 2 ); const offscreenCanvas = document.createElement( 'canvas' ); const offscreenContext = offscreenCanvas.getContext( '2d' ); offscreenCanvas.width = scaledWidth; offscreenCanvas.height = scaledHeight; offscreenContext.drawImage( image, 0, 0, scaledWidth, scaledHeight ); const imageData = offscreenContext.getImageData( 0, 0, scaledWidth, scaledHeight ); const data = imageData.data; for ( let y = 0; y < scaledHeight; y++ ) { for ( let x = 0; x < scaledWidth; x++ ) { const pixelIndex = ( y * scaledWidth + x ) * 4; const r = data[ pixelIndex ]; const g = data[ pixelIndex + 1 ]; const b = data[ pixelIndex + 2 ]; if ( r > IMAGE_THRESHOLD && g > IMAGE_THRESHOLD && b > IMAGE_THRESHOLD ) { const pixelIndex = ( startY + y ) * gridWidth + ( startX + x ); // imageBuffer[ pixelIndex ] = 0; imageBuffer[ pixelIndex ] = Math.floor( Math.random() * ( flameColorTableLength * 0.25 ) ); } } } } function spreadFrom() { const right = windDirection > 0 ? [ 1, 1, ...Array( Math.max( windStrength - 2, 0 ) ).fill( 1 ) ] : [ 1, 1 ]; const left = windDirection < 0 ? [ -1, -1, ...Array( Math.max( windStrength - 2, 0 ) ).fill( -1 ) ] : [ -1, -1 ]; pixelSpreadFrom = new Int16Array( [ gridWidth, gridWidth, gridWidth, gridWidth, gridWidth, gridWidth, gridWidth, gridWidth, gridWidth, gridWidth, // bottom ...right, // right ...left, // left -gridWidth, // top ] ); pixelSpreadFromLengthPreCalc = pixelSpreadFrom.length - 1; } //--- function getRandomNumber( min, max ) { return Math.random() * ( max - min ) + min; } function getRandomInt( min, max ) { randomIndex = ( randomIndex + 1 ) & RANDOM_TABLE_MASK; return ( ( randomTable[ randomIndex ] * ( max - min + 1 ) ) | 0 ) + min; } function interpolate( start, end, t ) { return Math.round( start + ( end - start ) * t ); } function createGradient( colors, flameColorDepth ) { const colorArray = new Uint8Array( flameColorDepth * 3 ); const segments = colors.length / 3 - 1; const colorsPerSegment = Math.floor( flameColorDepth / segments ); let index = 0; for ( let s = 0; s < segments; s++ ) { const startColor = colors.slice( s * 3, s * 3 + 3 ); const endColor = colors.slice( ( s + 1 ) * 3, ( s + 1 ) * 3 + 3 ); for ( let i = 0; i < colorsPerSegment; i++ ) { const t = i / colorsPerSegment; colorArray[ index ] = interpolate( startColor[ 0 ], endColor[ 0 ], t ); // R colorArray[ index + 1] = interpolate( startColor[ 1 ], endColor[ 1 ], t ); // G colorArray[ index + 2] = interpolate( startColor[ 2 ], endColor[ 2 ], t ); // B index += 3; } } const lastColor = colors.slice( -3 ); while ( index < colorArray.length ) { colorArray[ index ] = lastColor[ 0 ]; colorArray[ index + 1 ] = lastColor[ 1 ]; colorArray[ index + 2 ] = lastColor[ 2 ]; index += 3; } return colorArray; } //--- function cursorDownHandler( event ) { pointerDownButton = event.button; } function cursorUpHandler( event ) { pointerDownButton = -1; } function cursorEnterHandler( event ) { const rect = canvas.getBoundingClientRect(); const position = { x: 0, y: 0 }; if ( event.type === 'mouseenter' || event.type === 'pointerenter' ) { position.x = event.pageX - rect.left; //event.clientX position.y = event.pageY - rect.top; //event.clientY } else if ( event.type === 'touchstart' ) { position.x = event.touches[ 0 ].pageX - rect.left; position.y = event.touches[ 0 ].pageY - rect.top; } pointerPosSave.x = position.x / pixelSize | 0; pointerPosSave.y = position.y / pixelSize | 0; } function cursorLeaveHandler( event ) { pointerPos.x = center.x; pointerPos.y = center.y; pointerPosSave.x = pointerPos.x / pixelSize | 0; pointerPosSave.y = pointerPos.y / pixelSize | 0; pointerDownButton = -1; } function cursorMoveHandler( event ) { pointerPos = getCursorPosition( canvas, event ); } function getCursorPosition( element, event ) { const rect = element.getBoundingClientRect(); const position = { x: 0, y: 0 }; if ( event.type === 'mousemove' || event.type === 'pointermove' ) { position.x = event.pageX - rect.left; //event.clientX position.y = event.pageY - rect.top; //event.clientY } else if ( event.type === 'touchmove' ) { position.x = event.touches[ 0 ].pageX - rect.left; position.y = event.touches[ 0 ].pageY - rect.top; } return position; } //--- function trace() { const px = pointerPos.x / pixelSize | 0; const py = pointerPos.y / pixelSize | 0; const dx = px - pointerPosSave.x; const dy = py - pointerPosSave.y; if ( dx === 0 && dy === 0 ) { const index = py * gridWidth + px; pixelHolder[ index ] = 0; } else { const distanceSquared = dx * dx + dy * dy; const stepCount = Math.ceil( distanceSquared / pixelSizeSquared ); for ( let step = 0; step <= stepCount; step++ ) { const t = step / stepCount; const interpolatedX = ( px + t * dx ) | 0; const interpolatedY = ( py + t * dy ) | 0; const index = interpolatedY * gridWidth + interpolatedX; pixelHolder[ index ] = 0; } } pointerPosSave.x = px; pointerPosSave.y = py; } function draw() { for ( let i = 0; i < pixelHolderLength; i++ ) { let colorIndex = pixelHolder[ i ]; const direction = pixelSpreadFrom[ getRandomInt( 0, pixelSpreadFromLengthPreCalc ) ]; const neighborIndex = i + direction; const neighborColorIndex = pixelHolder[ neighborIndex ]; if ( neighborColorIndex < colorIndex ) { if ( pointerDownButton === -1 ) { colorIndex = neighborColorIndex + getRandomInt( -1, 4 ); } else { colorIndex = neighborColorIndex + getRandomInt( -1, 3 ); } } else { colorIndex++; } if ( colorIndex > flameColorTableLength ) { colorIndex = flameColorTableLength; } if ( colorIndex < 0 ) { colorIndex = 0; } const colorImageBufferIndex = pixelImageBufferHolder[ i ]; if ( colorImageBufferIndex < colorIndex ) { pixelBufferHolder[ i ] = colorImageBufferIndex; } else { pixelBufferHolder[ i ] = colorIndex; } } //--- for ( let i = 0; i < rocketHolderLength; i += ROCKET_PROPS ) { let rocketX = rocketHolder[ i ]; let rocketY = rocketHolder[ i + 1 ]; const rocketVX = rocketHolder[ i + 2 ]; const rocketVY = rocketHolder[ i + 3 ]; //--- rocketX += rocketVX; rocketY += rocketVY; if ( rocketY <= 0 ) { rocketX = Math.random() * w / pixelSize; rocketY = h / pixelSize; } //--- if ( rocketX > 0 && rocketX < gridWidth && rocketY < gridHeight ) { const pixelIndex = ( rocketY | 0 ) * gridWidth + ( rocketX | 0 ); pixelBufferHolder[ pixelIndex ] = 0; } //--- rocketHolder[ i ] = rocketX; rocketHolder[ i + 1 ] = rocketY; } //---- for ( let i = 0; i < pixelHolderLength; i++ ) { const colorIndex = pixelHolder[ i ]; if ( colorIndex < flameColorTableLength ) { if ( pixelSize > 0 ) { const gridY = ( i / gridWidth ) | 0; const gridX = i % gridWidth; for ( let offsetY = 0; offsetY < pixelSize; offsetY++ ) { for ( let offsetX = 0; offsetX < pixelSize; offsetX++ ) { const pixelIndex = ( ( gridY * pixelSize + offsetY ) * w + ( gridX * pixelSize + offsetX ) ) * 4; data[ pixelIndex ] = flameColorTable[ colorIndex * 3 ]; // R data[ pixelIndex + 1 ] = flameColorTable[ colorIndex * 3 + 1 ]; // G data[ pixelIndex + 2 ] = flameColorTable[ colorIndex * 3 + 2 ]; // B } } } else { const pixelIndex = i * 4; // 4 für RGBA data[ pixelIndex ] = flameColorTable[ colorIndex * 3 ]; // R data[ pixelIndex + 1 ] = flameColorTable[ colorIndex * 3 + 1 ]; // G data[ pixelIndex + 2 ] = flameColorTable[ colorIndex * 3 + 2 ]; // B } } pixelHolder[ i ] = pixelBufferHolder[ i ]; } } //--- function render( timestamp ) { trace(); draw(); //--- context.putImageData( imageData, 0, 0 ); //--- stats.update(); //--- animationFrame = requestAnimFrame( render ); } window.requestAnimFrame = ( () => { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame; } )(); window.cancelAnimFrame = ( () => { return window.cancelAnimationFrame || window.mozCancelAnimationFrame; } )(); //--- document.addEventListener( 'DOMContentLoaded', () => { init(); initGUI(); } );