diff --git a/blocks/waves/src/edit.js b/blocks/waves/src/edit.js index 6152b07a..823da0c3 100644 --- a/blocks/waves/src/edit.js +++ b/blocks/waves/src/edit.js @@ -23,6 +23,8 @@ import { useDispatch, useSelect } from '@wordpress/data'; import { useState, useEffect, useRef } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; +const { run, parseColor, renderPreview } = window.a8cColorEffects; + const DEFAULT_COLORS = { color1: '#000', color2: '#555', @@ -129,12 +131,15 @@ function Edit( { attributes, className, isSelected, setAttributes } ) { DEFAULT_COLORS.color4, }; - const renderPreview = ( newAttributes = {} ) => - window.a8cColorEffects.renderPreview( { + const updatePreview = ( newAttributes = {} ) => + renderPreview( { complexity: attributes.complexity, mouseSpeed: 1, fluidSpeed: 1, - ...colors, + color1: parseColor( colors.color1 ), + color2: parseColor( colors.color2 ), + color3: parseColor( colors.color3 ), + color4: parseColor( colors.color4 ), ...newAttributes, } ); @@ -149,7 +154,7 @@ function Edit( { attributes, className, isSelected, setAttributes } ) { // Save the initial preview in the attributes if ( attributes.previewImage === undefined ) { - const previewImage = renderPreview(); + const previewImage = updatePreview(); setAttributes( { previewImage } ); } }, [] ); @@ -162,10 +167,39 @@ function Edit( { attributes, className, isSelected, setAttributes } ) { backgroundImage: `url( "${ attributes.previewImage }" )`, }; + // To avoid recompiling shaders every frame uniform variables need to be + // mutable so a new reference doesn't have to be passed to run. + const dataset = useRef( { + complexity: attributes.complexity, + mouseSpeed: attributes.mouseSpeed, + fluidSpeed: attributes.fluidSpeed, + color1: parseColor( colors.color1 ), + color2: parseColor( colors.color2 ), + color3: parseColor( colors.color3 ), + color4: parseColor( colors.color4 ), + } ); + useEffect( () => { + dataset.current.complexity = attributes.complexity; + dataset.current.mouseSpeed = attributes.mouseSpeed; + dataset.current.fluidSpeed = attributes.fluidSpeed; + dataset.current.color1 = parseColor( colors.color1 ); + dataset.current.color2 = parseColor( colors.color2 ); + dataset.current.color3 = parseColor( colors.color3 ); + dataset.current.color4 = parseColor( colors.color4 ); + }, [ + attributes.complexity, + attributes.mouseSpeed, + attributes.fluidSpeed, + colors.color1, + colors.color2, + colors.color3, + colors.color4, + ] ); + const canvasRef = useRef(); useEffect( () => { - return window.a8cColorEffects.run( canvasRef.current ); - }, [ canvasRef.current ] ); + return run( canvasRef.current, dataset.current ); + }, [ canvasRef.current, dataset.current ] ); return ( <> @@ -175,7 +209,7 @@ function Edit( { attributes, className, isSelected, setAttributes } ) { label={ __( 'Complexity', 'waves' ) } value={ attributes.complexity } onChange={ ( complexity ) => { - const previewImage = renderPreview( { + const previewImage = updatePreview( { complexity, } ); setAttributes( { complexity, previewImage } ); @@ -209,9 +243,9 @@ function Edit( { attributes, className, isSelected, setAttributes } ) { { label: __( 'Color 1', 'waves' ), value: colors.color1, - onChange: ( color1 ) => { - const previewImage = renderPreview( { - color1, + onChange: ( color1 = colors.color1 ) => { + const previewImage = updatePreview( { + color1: parseColor( color1 ), } ); setAttributes( { color1, previewImage } ); }, @@ -219,9 +253,9 @@ function Edit( { attributes, className, isSelected, setAttributes } ) { { label: __( 'Color 2', 'waves' ), value: colors.color2, - onChange: ( color2 ) => { - const previewImage = renderPreview( { - color2, + onChange: ( color2 = colors.color2 ) => { + const previewImage = updatePreview( { + color2: parseColor( color2 ), } ); setAttributes( { color2, previewImage } ); }, @@ -229,9 +263,9 @@ function Edit( { attributes, className, isSelected, setAttributes } ) { { label: __( 'Color 3', 'waves' ), value: colors.color3, - onChange: ( color3 ) => { - const previewImage = renderPreview( { - color3, + onChange: ( color3 = colors.color3 ) => { + const previewImage = updatePreview( { + color3: parseColor( color3 ), } ); setAttributes( { color3, previewImage } ); }, @@ -239,9 +273,9 @@ function Edit( { attributes, className, isSelected, setAttributes } ) { { label: __( 'Color 4', 'waves' ), value: colors.color4, - onChange: ( color4 ) => { - const previewImage = renderPreview( { - color4, + onChange: ( color4 = colors.color4 ) => { + const previewImage = updatePreview( { + color4: parseColor( color4 ), } ); setAttributes( { color4, previewImage } ); }, @@ -289,16 +323,7 @@ function Edit( { attributes, className, isSelected, setAttributes } ) { showHandle={ isSelected } >
- +
{ document .querySelectorAll( '.wp-block-a8c-waves canvas' ) - .forEach( a8cColorEffects.run ); + .forEach( ( canvas ) => { + const dataset = { + color1: parseColor( canvas.dataset.color1 ), + color2: parseColor( canvas.dataset.color2 ), + color3: parseColor( canvas.dataset.color3 ), + color4: parseColor( canvas.dataset.color4 ), + complexity: Number.parseInt( + canvas.dataset.complexity, + 10 + ), + mouseSpeed: Number.parseFloat( + canvas.dataset.mouseSpeed + ), + fluidSpeed: Number.parseFloat( + canvas.dataset.fluidSpeed + ), + }; + run( canvas, dataset ); + } ); } ); } } )( ( twgl ) => { @@ -115,7 +134,7 @@ function inverse( [ x1, x2 ], [ y1, y2 ] ) { const a = ( x1 * x2 * ( -y1 + y2 ) ) / ( x1 - x2 ); const b = ( x1 * y1 - x2 * y2 ) / ( x1 - x2 ); - return function( x ) { + return function ( x ) { return a / x + b; }; } @@ -157,30 +176,6 @@ textureInfo: twgl.createFramebufferInfo( gl, null, 512, 512 ), } ); - /** - * Convert a hex color string to a WebGL color vector. - * - * @param {string} color Hex color string (#FFFFFF or #FFF) - * @return {number[]} RGB array for WebGL - */ - function parseColor( color ) { - let r = '0'; - let g = '0'; - let b = '0'; - - if ( color.length === 7 ) { - r = '0x' + color[ 1 ] + color[ 2 ]; - g = '0x' + color[ 3 ] + color[ 4 ]; - b = '0x' + color[ 5 ] + color[ 6 ]; - } else if ( color.length === 4 ) { - r = '0x' + color[ 1 ] + color[ 1 ]; - g = '0x' + color[ 2 ] + color[ 2 ]; - b = '0x' + color[ 3 ] + color[ 3 ]; - } - - return [ r / 0xff, g / 0xff, b / 0xff ]; - } - /** * Draw an individual block. * @@ -193,12 +188,29 @@ renderLiquidEffect( gl, state, program ); } + /** + * The parsed dataset from the canvas. + * + * @typedef {Object} Dataset + * @property {number[]} color1 First color of the gradient. + * @property {number[]} color2 Second color of the gradient. + * @property {number[]} color3 Third color of the gradient. + * @property {number[]} color4 Fourth color of the gradient. + * @property {number} complexity Integer complexity of the animation. + * @property {number} mouseSpeed Float mouse speed of the animation. + * @property {number} fluidSpeed Float fluid speed of the animation. + */ + /** * Draw the custom gradient to the framebuffer. * * @param {WebGLRenderingContext} gl WebGL rendering context - * @param {Object} state Data state for the rendering frame - * @param {Object} program Collection of program info and buffers + * @param {Object} state State for the rendering frame. + * @param {Dataset} state.dataset Dataset to use for rendering. + * @param {Object} program Collection of program info and buffers. + * @param {Object} program.programInfoGradient Gradient program info. + * @param {Object} program.screenBufferInfo Screen buffer info. + * @param {Object} program.textureInfo Texture info. */ function renderGradient( gl, @@ -206,10 +218,10 @@ { programInfoGradient, screenBufferInfo, textureInfo } ) { const uniforms = { - color1: parseColor( dataset.color1 ), - color2: parseColor( dataset.color2 ), - color3: parseColor( dataset.color3 ), - color4: parseColor( dataset.color4 ), + color1: dataset.color1, + color2: dataset.color2, + color3: dataset.color3, + color4: dataset.color4, }; twgl.bindFramebufferInfo( gl, textureInfo ); @@ -229,9 +241,15 @@ /** * Draw the liquid effect to the canvas. * - * @param {WebGLRenderingContext} gl WebGL rendering context - * @param {Object} state Data state for the rendering frame - * @param {Object} program Collection of program info and buffers + * @param {WebGLRenderingContext} gl WebGL rendering context. + * @param {Object} state Data state for the rendering frame. + * @param {Dataset} state.dataset Dataset to use for rendering. + * @param {number[]} state.mouse Mouse position [ x, y ]. + * @param {number} state.time Current program time. + * @param {Object} program Collection of program info and buffers. + * @param {Object} program.programInfoEffectPass Effect pass program info. + * @param {Object} program.screenBufferInfo Screen buffer info. + * @param {Object} program.textureInfo Texture info. */ function renderLiquidEffect( gl, @@ -239,9 +257,7 @@ { programInfoEffectPass, screenBufferInfo, textureInfo } ) { const resolution = [ gl.canvas.width, gl.canvas.height ]; - const complexity = Number.parseInt( dataset.complexity, 10 ); - const mouseSpeed = Number.parseFloat( dataset.mouseSpeed ); - const fluidSpeed = Number.parseFloat( dataset.fluidSpeed ); + const { complexity, mouseSpeed, fluidSpeed } = dataset; const uniforms = { // Required in the vertex shader to prevent stretching @@ -278,6 +294,36 @@ } return { + /** + * Convert a hex color string to a WebGL color vector. + * + * @param {string} color Hex color string (#FFFFFF or #FFF) + * @return {number[]} RGB array for WebGL + */ + parseColor( color ) { + let r = '0'; + let g = '0'; + let b = '0'; + + if ( color.length === 7 ) { + r = '0x' + color[ 1 ] + color[ 2 ]; + g = '0x' + color[ 3 ] + color[ 4 ]; + b = '0x' + color[ 5 ] + color[ 6 ]; + } else if ( color.length === 4 ) { + r = '0x' + color[ 1 ] + color[ 1 ]; + g = '0x' + color[ 2 ] + color[ 2 ]; + b = '0x' + color[ 3 ] + color[ 3 ]; + } + + return [ r / 0xff, g / 0xff, b / 0xff ]; + }, + + /** + * Render a 512x512 px frame of the animation to use as a preview. + * + * @param {Dataset} dataset Dataset to use for rendering. + * @return {string} Data URI of a rendered frame. + */ renderPreview( dataset ) { const canvas = document.createElement( 'canvas' ); canvas.width = canvas.height = '512'; @@ -296,7 +342,14 @@ return gl.canvas.toDataURL(); }, - run( canvas ) { + + /** + * Runs the animation. + * + * @param {HTMLCanvasElement} canvas Canvas to draw on. + * @param {Dataset} dataset Reference to a parsed dataset. + */ + run( canvas, dataset ) { const shouldAnimate = ! window.matchMedia( '(prefers-reduced-motion: reduce)' ).matches; @@ -315,7 +368,7 @@ const program = init( gl ); const state = { - dataset: canvas.dataset, + dataset, mouse: [ 0, 0 ], time: window.performance.now(), rafId: 0,