mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-13 17:27:09 +00:00
Vertical two-up (#100)
* Fixing bad property name. * Allowing two-up to work vertically at smaller widths. * Switching to orientation attr * Fixing type and getter/setter behaviour
This commit is contained in:
@@ -1,14 +1,17 @@
|
|||||||
import * as styles from './styles.css';
|
import * as styles from './styles.css';
|
||||||
import { PointerTracker, Pointer } from '../../../../lib/PointerTracker';
|
import { PointerTracker, Pointer } from '../../../../lib/PointerTracker';
|
||||||
|
|
||||||
const legacyClipCompat = 'legacy-clip-compat';
|
const legacyClipCompatAttr = 'legacy-clip-compat';
|
||||||
|
const orientationAttr = 'orientation';
|
||||||
|
|
||||||
|
type TwoUpOrientation = 'horizontal' | 'vertical';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A split view that the user can adjust. The first child becomes
|
* A split view that the user can adjust. The first child becomes
|
||||||
* the left-hand side, and the second child becomes the right-hand side.
|
* the left-hand side, and the second child becomes the right-hand side.
|
||||||
*/
|
*/
|
||||||
export default class TwoUp extends HTMLElement {
|
export default class TwoUp extends HTMLElement {
|
||||||
static get observedAttributes () { return [legacyClipCompat]; }
|
static get observedAttributes() { return [orientationAttr]; }
|
||||||
|
|
||||||
private readonly _handle = document.createElement('div');
|
private readonly _handle = document.createElement('div');
|
||||||
/**
|
/**
|
||||||
@@ -52,39 +55,65 @@ export default class TwoUp extends HTMLElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback () {
|
connectedCallback() {
|
||||||
this._childrenChange();
|
this._childrenChange();
|
||||||
if (!this._everConnected) {
|
if (!this._everConnected) {
|
||||||
// Set the initial position of the handle.
|
this._resetPosition();
|
||||||
requestAnimationFrame(() => {
|
|
||||||
const bounds = this.getBoundingClientRect();
|
|
||||||
this._position = bounds.width / 2;
|
|
||||||
this._setPosition();
|
|
||||||
});
|
|
||||||
this._everConnected = true;
|
this._everConnected = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
attributeChangedCallback(name: string) {
|
||||||
|
if (name === orientationAttr) {
|
||||||
|
this._resetPosition();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _resetPosition() {
|
||||||
|
// Set the initial position of the handle.
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
const bounds = this.getBoundingClientRect();
|
||||||
|
this._position = (this.orientation === 'vertical' ? bounds.height : bounds.width) / 2;
|
||||||
|
this._setPosition();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If true, this element works in browsers that don't support clip-path (Edge).
|
* If true, this element works in browsers that don't support clip-path (Edge).
|
||||||
* However, this means you'll have to set the height of this element manually.
|
* However, this means you'll have to set the height of this element manually.
|
||||||
*/
|
*/
|
||||||
get noClipPathCompat () {
|
get legacyClipCompat() {
|
||||||
return this.hasAttribute(legacyClipCompat);
|
return this.hasAttribute(legacyClipCompatAttr);
|
||||||
}
|
}
|
||||||
|
|
||||||
set noClipPathCompat (val: boolean) {
|
set legacyClipCompat(val: boolean) {
|
||||||
if (val) {
|
if (val) {
|
||||||
this.setAttribute(legacyClipCompat, '');
|
this.setAttribute(legacyClipCompatAttr, '');
|
||||||
} else {
|
} else {
|
||||||
this.removeAttribute(legacyClipCompat);
|
this.removeAttribute(legacyClipCompatAttr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split vertically rather than horizontally.
|
||||||
|
*/
|
||||||
|
get orientation(): TwoUpOrientation {
|
||||||
|
const value = this.getAttribute(orientationAttr);
|
||||||
|
|
||||||
|
// This mirrors the behaviour of input.type, where setting just sets the attribute, but getting
|
||||||
|
// returns the value only if it's valid.
|
||||||
|
if (value && value.toLowerCase() === 'vertical') return 'vertical';
|
||||||
|
return 'horizontal';
|
||||||
|
}
|
||||||
|
|
||||||
|
set orientation(val: TwoUpOrientation) {
|
||||||
|
this.setAttribute(orientationAttr, val);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when element's child list changes
|
* Called when element's child list changes
|
||||||
*/
|
*/
|
||||||
private _childrenChange () {
|
private _childrenChange() {
|
||||||
// Ensure the handle is the last child.
|
// Ensure the handle is the last child.
|
||||||
// The CSS depends on this.
|
// The CSS depends on this.
|
||||||
if (this.lastElementChild !== this._handle) {
|
if (this.lastElementChild !== this._handle) {
|
||||||
@@ -95,15 +124,20 @@ export default class TwoUp extends HTMLElement {
|
|||||||
/**
|
/**
|
||||||
* Called when a pointer moves.
|
* Called when a pointer moves.
|
||||||
*/
|
*/
|
||||||
private _pointerChange (startPoint: Pointer, currentPoint: Pointer) {
|
private _pointerChange(startPoint: Pointer, currentPoint: Pointer) {
|
||||||
|
const pointAxis = this.orientation === 'vertical' ? 'clientY' : 'clientX';
|
||||||
|
const dimensionAxis = this.orientation === 'vertical' ? 'height' : 'width';
|
||||||
const bounds = this.getBoundingClientRect();
|
const bounds = this.getBoundingClientRect();
|
||||||
this._position = this._positionOnPointerStart + (currentPoint.clientX - startPoint.clientX);
|
|
||||||
|
this._position = this._positionOnPointerStart +
|
||||||
|
(currentPoint[pointAxis] - startPoint[pointAxis]);
|
||||||
|
|
||||||
// Clamp position to element bounds.
|
// Clamp position to element bounds.
|
||||||
this._position = Math.max(0, Math.min(this._position, bounds.width));
|
this._position = Math.max(0, Math.min(this._position, bounds[dimensionAxis]));
|
||||||
this._setPosition();
|
this._setPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setPosition () {
|
private _setPosition() {
|
||||||
this.style.setProperty('--split-point', `${this._position}px`);
|
this.style.setProperty('--split-point', `${this._position}px`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,13 @@ interface Window {
|
|||||||
Touch: typeof Touch;
|
Touch: typeof Touch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface TwoUpAttributes extends JSX.HTMLAttributes {
|
||||||
|
'orientation'?: string;
|
||||||
|
'legacy-clip-compat'?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
declare namespace JSX {
|
declare namespace JSX {
|
||||||
interface IntrinsicElements {
|
interface IntrinsicElements {
|
||||||
'two-up': HTMLAttributes;
|
'two-up': TwoUpAttributes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,17 @@ two-up[legacy-clip-compat] > :not(.twoUpHandle) {
|
|||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
two-up[orientation='vertical'] .twoUpHandle {
|
||||||
|
width: auto;
|
||||||
|
height: 10px;
|
||||||
|
transform: translateY(var(--split-point)) translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
two-up[orientation='vertical'] .twoUpHandle::after {
|
||||||
|
width: 40px;
|
||||||
|
height: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
two-up > :nth-child(1):not(.twoUpHandle) {
|
two-up > :nth-child(1):not(.twoUpHandle) {
|
||||||
-webkit-clip-path: inset(0 calc(100% - var(--split-point)) 0 0);
|
-webkit-clip-path: inset(0 calc(100% - var(--split-point)) 0 0);
|
||||||
clip-path: inset(0 calc(100% - var(--split-point)) 0 0);
|
clip-path: inset(0 calc(100% - var(--split-point)) 0 0);
|
||||||
@@ -46,6 +57,16 @@ two-up > :nth-child(2):not(.twoUpHandle) {
|
|||||||
clip-path: inset(0 0 0 var(--split-point));
|
clip-path: inset(0 0 0 var(--split-point));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
two-up[orientation='vertical'] > :nth-child(1):not(.twoUpHandle) {
|
||||||
|
-webkit-clip-path: inset(0 0 calc(100% - var(--split-point)) 0);
|
||||||
|
clip-path: inset(0 0 calc(100% - var(--split-point)) 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
two-up[orientation='vertical'] > :nth-child(2):not(.twoUpHandle) {
|
||||||
|
-webkit-clip-path: inset(var(--split-point) 0 0 0);
|
||||||
|
clip-path: inset(var(--split-point) 0 0 0);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Even in legacy-clip-compat, prefer clip-path if it's supported.
|
Even in legacy-clip-compat, prefer clip-path if it's supported.
|
||||||
It performs way better in Safari.
|
It performs way better in Safari.
|
||||||
@@ -58,4 +79,12 @@ two-up > :nth-child(2):not(.twoUpHandle) {
|
|||||||
two-up[legacy-clip-compat] > :nth-child(2):not(.twoUpHandle) {
|
two-up[legacy-clip-compat] > :nth-child(2):not(.twoUpHandle) {
|
||||||
clip: rect(auto auto auto var(--split-point));
|
clip: rect(auto auto auto var(--split-point));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
two-up[orientation='vertical'][legacy-clip-compat] > :nth-child(1):not(.twoUpHandle) {
|
||||||
|
clip: rect(auto auto var(--split-point) auto);
|
||||||
|
}
|
||||||
|
|
||||||
|
two-up[orientation='vertical'][legacy-clip-compat] > :nth-child(2):not(.twoUpHandle) {
|
||||||
|
clip: rect(var(--split-point) auto auto auto);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,21 +6,31 @@ import * as style from './style.scss';
|
|||||||
import { bind, drawBitmapToCanvas, linkRef } from '../../lib/util';
|
import { bind, drawBitmapToCanvas, linkRef } from '../../lib/util';
|
||||||
import { twoUpHandle } from './custom-els/TwoUp/styles.css';
|
import { twoUpHandle } from './custom-els/TwoUp/styles.css';
|
||||||
|
|
||||||
type Props = {
|
interface Props {
|
||||||
leftImg: ImageBitmap,
|
leftImg: ImageBitmap;
|
||||||
rightImg: ImageBitmap,
|
rightImg: ImageBitmap;
|
||||||
};
|
}
|
||||||
|
|
||||||
type State = {};
|
interface State {
|
||||||
|
verticalTwoUp: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export default class Output extends Component<Props, State> {
|
export default class Output extends Component<Props, State> {
|
||||||
state: State = {};
|
widthQuery = window.matchMedia('(min-width: 500px)');
|
||||||
|
state: State = {
|
||||||
|
verticalTwoUp: !this.widthQuery.matches,
|
||||||
|
};
|
||||||
canvasLeft?: HTMLCanvasElement;
|
canvasLeft?: HTMLCanvasElement;
|
||||||
canvasRight?: HTMLCanvasElement;
|
canvasRight?: HTMLCanvasElement;
|
||||||
pinchZoomLeft?: PinchZoom;
|
pinchZoomLeft?: PinchZoom;
|
||||||
pinchZoomRight?: PinchZoom;
|
pinchZoomRight?: PinchZoom;
|
||||||
retargetedEvents = new WeakSet<Event>();
|
retargetedEvents = new WeakSet<Event>();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.widthQuery.addListener(this.onMobileWidthChange);
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (this.canvasLeft) {
|
if (this.canvasLeft) {
|
||||||
drawBitmapToCanvas(this.canvasLeft, this.props.leftImg);
|
drawBitmapToCanvas(this.canvasLeft, this.props.leftImg);
|
||||||
@@ -39,8 +49,15 @@ export default class Output extends Component<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps: Props) {
|
shouldComponentUpdate(nextProps: Props, nextState: State) {
|
||||||
return this.props.leftImg !== nextProps.leftImg || this.props.rightImg !== nextProps.rightImg;
|
return this.props.leftImg !== nextProps.leftImg ||
|
||||||
|
this.props.rightImg !== nextProps.rightImg ||
|
||||||
|
this.state.verticalTwoUp !== nextState.verticalTwoUp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@bind
|
||||||
|
onMobileWidthChange() {
|
||||||
|
this.setState({ verticalTwoUp: !this.widthQuery.matches });
|
||||||
}
|
}
|
||||||
|
|
||||||
@bind
|
@bind
|
||||||
@@ -80,10 +97,11 @@ export default class Output extends Component<Props, State> {
|
|||||||
this.pinchZoomLeft.dispatchEvent(clonedEvent);
|
this.pinchZoomLeft.dispatchEvent(clonedEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
render({ leftImg, rightImg }: Props, { }: State) {
|
render({ leftImg, rightImg }: Props, { verticalTwoUp }: State) {
|
||||||
return (
|
return (
|
||||||
<div class={style.output}>
|
<div class={style.output}>
|
||||||
<two-up
|
<two-up
|
||||||
|
orientation={verticalTwoUp ? 'vertical' : 'horizontal'}
|
||||||
// Event redirecting. See onRetargetableEvent.
|
// Event redirecting. See onRetargetableEvent.
|
||||||
onTouchStartCapture={this.onRetargetableEvent}
|
onTouchStartCapture={this.onRetargetableEvent}
|
||||||
onTouchEndCapture={this.onRetargetableEvent}
|
onTouchEndCapture={this.onRetargetableEvent}
|
||||||
|
|||||||
Reference in New Issue
Block a user