block.json
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "wlc/image-comparison",
"title": "Image Comparison",
"category": "theme",
"icon": "image-flip-horizontal",
"description": "Image comparison",
"keywords": [ "image", "comparison" ],
"version": "1.0.0",
"textdomain": "WLC",
"supports": {
"renaming": true,
"align": true,
"anchor": true,
"shadow": true,
"spacing": {
"margin": true
}
},
"attributes": {
"vertical": {
"type": "boolean",
"default": false
},
"grabLength": {
"type": "string",
"default": 100
},
"grabWidth": {
"type": "string",
"default": 5
},
"displayIcon": {
"type": "boolean",
"default": true
}
},
"editorScript": "file:./index.js",
"viewScript": "file:./view.js",
"viewStyle": "file:./style-index.css",
"editorStyle": "file:./index.css"
}
edit.js
import { useBlockProps, InspectorControls,InnerBlocks } from '@wordpress/block-editor'
import { PanelBody, ToggleControl, TextControl } from '@wordpress/components'
import { __ } from '@wordpress/i18n'
const edit = ( { attributes: {vertical, grabLength, grabWidth, displayIcon }, setAttributes } ) => {
const blockProps = useBlockProps( {
className: `wlc-image-comparison ${ vertical ? 'vertical' : '' }`
} );
const allowedBlocks = [ 'core/image' ]
const innerTemplate = [
[ 'core/image', { className: 'first' } ],
[ 'core/image', { className: 'second' } ]
]
const setVertical = vertical => setAttributes( { vertical } );
const setGrabLength = grabLength => setAttributes( { grabLength } );
const setGrabWidth = grabWidth => setAttributes( { grabWidth } );
const setDisplayIcon = displayIcon => setAttributes( { displayIcon } );
const grabStyle = {
[vertical ? 'width' : 'height' ]: `${grabLength}%`,
[vertical ? 'height' : 'width' ]: `${grabWidth/16}rem`,
'--display-icon': displayIcon ? 'block' : 'none'
}
return (
<>
<InspectorControls>
<PanelBody title={ __( 'Comparison Setings', 'WLC') }>
<ToggleControl
label={ __( 'Vertical', 'WLC') }
checked={ vertical }
onChange={ setVertical }
/>
<ToggleControl
label={ __( 'Handle Icon', 'WLC') }
checked={ displayIcon }
onChange={ setDisplayIcon }
/>
<TextControl
label={ __( 'Divider length in %', 'WLC') }
type="number"
min="0"
max="100"
value={ grabLength }
onChange={ setGrabLength }
/>
<TextControl
label={ __( 'Divider width', 'WLC') }
type="number"
min="0"
max="10"
value={ grabWidth }
onChange={ setGrabWidth }
/>
</PanelBody>
</InspectorControls>
<div { ...blockProps }>
<InnerBlocks
template={ innerTemplate }
allowedBlocks={ allowedBlocks }
templateLock="all"
/>
<div className="grab" style={ grabStyle }/>
</div>
</>
)
}
export default edit;
editor.css
.wlc-image-comparison {
& .block-editor-block-list__layout {
@apply grid grid-cols-2 grid-rows-2 gap-5;
}
.wp-block-image {
@apply row-start-1 row-span-2 col-start-1 col-span-2;
&:has(.components-placeholder) {
@apply row-start-1 row-span-2 col-span-1 first:col-start-2 last:col-start-1;
}
&.first {
&.is-selected {
@apply order-2;
}
}
&.second {
&:not(:has(.components-placeholder)) {
clip-path: xywh( 0 0 50% 100%);
.vertical & {
clip-path: xywh( 0 0 100% 50%);
}
}
&.is-selected {
clip-path:none !important;
}
}
}
& > .grab {
@apply absolute block bg-white w-[.3125rem] rounded-full;
@apply before:block before:absolute;
@apply before:content-icon-arrows-horizontal before:bg-white before:w-8 before:h-8;
@apply before:leading-0 before:content-center before:text-center before:rounded-full;
@apply before:left-1/2 before:-translate-x-1/2 before:top-1/2 before:-translate-y-1/2;
&::before {
display: var(--display-icon, block);
}
:not(.vertical) > & {
@apply w-[.3125rem] h-full top-1/2 -translate-y-1/2 left-[var(--x,50%)] -translate-x-1/2;
}
.vertical > & {
@apply h-[.3125rem] w-full left-1/2 -translate-x-1/2 top-[var(--y,50%)] -translate-y-1/2;
@apply before:rotate-90;
}
}
}
index.js
import { registerBlockType } from "@wordpress/blocks";
import metadata from './block.json';
import save from "./save";
import edit from './edit';
import './style.css';
import './editor.css';
registerBlockType( metadata, {
edit: edit,
save: save,
});
save.js
import { useBlockProps, InnerBlocks } from '@wordpress/block-editor'
const save = ( { attributes: { vertical, grabLength, grabWidth, displayIcon } } ) => {
const blockProps = useBlockProps.save( {
className: 'wlc-image-comparison'
} );
const grabStyle = {
[vertical ? 'width' : 'height' ]: `${grabLength}%`,
[vertical ? 'height' : 'width' ]: `${grabWidth/16}rem`,
'--display-icon': displayIcon ? 'block' : 'none'
}
const context = JSON.stringify( {
x: vertical ? '100%' : '50%',
y: vertical ? '50%': '100%',
vertical
} )
return (
<div
{ ...blockProps}
data-context={context}
>
<InnerBlocks.Content/>
<div className="grab" style={ grabStyle }></div>
</div>
)
}
export default save;
style.css
.wlc-image-comparison {
@apply relative grid grid-rows-1 grid-cols-1;
& > .wp-block-image {
& {
@apply row-start-1 col-start-1;
}
&.second {
clip-path: xywh(0 0 var(--x, 50%) var(--y, 50%) );
}
img {
@apply pointer-events-none select-none;
}
}
& > .grab {
@apply absolute block bg-white w-[.3125rem] rounded-full;
@apply before:block before:absolute;
@apply before:content-icon-arrows-horizontal before:bg-white before:w-8 before:h-8;
@apply before:leading-0 before:content-center before:text-center before:rounded-full;
@apply before:left-1/2 before:-translate-x-1/2 before:top-1/2 before:-translate-y-1/2;
&::before {
display: var(--display-icon, block);
}
:not(.vertical) > & {
@apply w-[.3125rem] h-full top-1/2 -translate-y-1/2 left-[var(--x,50%)] -translate-x-1/2;
}
.vertical > & {
@apply h-[.3125rem] w-full left-1/2 -translate-x-1/2 top-[var(--y,50%)] -translate-y-1/2;
@apply before:rotate-90;
}
}
}
view.js
const comparisons = document.querySelectorAll('.wlc-image-comparison');
const getContext = element => JSON.parse( element.dataset.context )
const setProps = (element, x, y) => {
element.style.setProperty( '--x', x )
element.style.setProperty( '--y', y )
}
const minmax = value => Math.max(Math.min(value, 100), 0 )
const setCoords = ({ currentTarget, clientX, clientY }) => {
const { vertical } = getContext(currentTarget)
const { left, top } = currentTarget.getBoundingClientRect()
const x = (clientX-left)/currentTarget.clientWidth*100
const y = (clientY-top)/currentTarget.clientHeight*100
const coords = {
x: vertical ? 100 : minmax(x),
y: vertical ? minmax(y) : 100,
};
setProps( currentTarget, `${coords.x}%`, `${coords.y}%` )
}
const setComparison = comparison => {
const { x, y, vertical } = getContext(comparison);
setProps( comparison, x, y )
comparison.classList.toggle('vertical', vertical );
comparison.querySelectorAll('.wp-block-image img').forEach(img => {
img.draggable=false;
})
comparison.addEventListener('mousedown', e => {
setCoords(e);
comparison.addEventListener('mousemove', setCoords );
document.addEventListener('mouseup', e => {
comparison.removeEventListener('mousemove', setCoords);
})
}, true )
}
comparisons.forEach( setComparison )