Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

.other-box {
background-color: darkgray;
color: white;
}

.other-box-small {
height: 100px;
width: 500px;
}

.other-box-large {
height: 300px;
width: 800px;
}
17 changes: 12 additions & 5 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,17 @@ function App() {
<button onClick={() => setBool(!bool)}>Swap Layout</button>
<button onClick={() => setOtherBool(!otherBool)}>Swap Other Bool</button>

<Transition className={'other-box ' + newBoxClassName}>
<Transition.PreserveAspectRatio>
There is a bunch of text in here... awesome! Like so awesome, serious! How about a bunch more, that sounds nice. Ok, just a bit more.
</Transition.PreserveAspectRatio>
<Transition className={'other-box ' + newBoxClassName} style={{position: 'relative'}}>
<div style={{border: '1px solid black'}}>
<Transition.ReverseScale style={{transformOrigin: '0 0 !important'}}>
<div>hey</div>
<div>what</div>
</Transition.ReverseScale>
</div>
<Transition.ReverseScale style={{transformOrigin: '0 top !important'}}>Hello World</Transition.ReverseScale>
<Transition.ReverseScale style={{position: 'absolute', right: 0, bottom: 0}}>
There is a bunch of text
</Transition.ReverseScale>
</Transition>

<div className={bool ? 'grid' : 'expanded'}>
Expand All @@ -27,7 +34,7 @@ function App() {
>
<Transition.PreserveAspectRatio style={{ color: 'blue' }}>
{bool && <div>HELLO</div>}
{!bool && <div>Quis blandit turpis cursus in hac habitasse platea dictumst. Quis eleifend quam adipiscing vitae proin sagittis nisl. Aliquet lectus proin nibh nisl. Interdum velit laoreet id donec ultrices tincidunt arcu non. Duis ut diam quam nulla porttitor massa id neque aliquam. Libero id faucibus nisl tincidunt eget nullam non nisi. Ut diam quam nulla porttitor massa id neque aliquam. Morbi non arcu risus quis varius quam quisque. Iaculis eu non diam phasellus vestibulum lorem sed. Congue mauris rhoncus aenean vel elit scelerisque mauris. Pulvinar elementum integer enim neque volutpat ac tincidunt vitae semper. Tincidunt id aliquet risus feugiat in ante metus. Vel quam elementum pulvinar etiam non quam lacus suspendisse. Dolor sit amet consectetur adipiscing elit duis. Pretium aenean pharetra magna ac. Imperdiet nulla malesuada pellentesque elit eget gravida.</div>}
{!bool && <div>Quis blandit turpis cursus in hac habitasse platea dictumst. Quis eleifend quam adipiscing vitae proin sagittis nisl. Aliquet lectus proin nibh nisl. Interdum velit laoreet id donec ultrices tincidunt arcu non. Duis ut diam quam nulla porttitor massa id neque aliquam. Libero id faucibus nisl tincidunt eget nullam non nisi. Ut diam quam nulla porttitor massa id neque aliquam. Morbi non arcu risus quis varius quam quisque. Iaculis eu non diam phasellus vestibulum lorem sed. Congue mauris rhoncus aenean vel elit scelerisque mauris. Pulvinar elementum integer enim neque volutpat ac tincidunt vitae semper. Tincidunt id aliquet risus feugiat in ante metus. Vel quam elementum pulvinar etiam non quam lacus suspendisse. Dolor sit amet consectetur adipiscing elit duis. Pretium aenean pharetra magna ac. Imperdiet nulla malesuada pellentesque elit eget gravida.</div>}
</Transition.PreserveAspectRatio>
</Transition>

Expand Down
3 changes: 2 additions & 1 deletion src/stories/Transition.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React from 'react';
import { Transition } from '../transition';
import App from '../App';

export const TransitionStory = () => (
<Transition>
Story Skeleton
<App />
</Transition>
);
14 changes: 13 additions & 1 deletion src/transition/transition.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export class Transition extends Component<TransitionProps, TransitionState> {
this._resetElements();
}

const { style = {}, duration = 200 } = this.props;
const { style = {}, duration = 1000 } = this.props;

const trueOuterPosition = getPositionRelativeToParent(this._containerRef.current);
const trueAspectRatio = trueOuterPosition.width / trueOuterPosition.height;
Expand Down Expand Up @@ -174,9 +174,21 @@ export class Transition extends Component<TransitionProps, TransitionState> {
.forEach(([selector, transform]) => {
this._containerRef.current?.querySelectorAll<HTMLElement>(selector).forEach(element => {
const userStyle = JSON.parse(element.getAttribute('data-style') || '');
// This "solves" stacking reverse scales, but eliminates the performance benefits of transform and introduces a jitter
// const t = transform.toObject();
// const scaleX = parseFloat(t.transform.replace(/^.*scaleX\((\d+\.?\d*)\).*$/, '$1'));
// const scaleY = parseFloat(t.transform.replace(/^.*scaleY\((\d+\.?\d*)\).*$/, '$1'));
// const dimensions = JSON.parse(element.getAttribute('originalDimensions') ?? JSON.stringify({
// clientWidth: element.clientWidth,
// clientHeight: element.clientHeight,
// }));
// element.setAttribute('originalDimensions', JSON.stringify(dimensions));
element.style.cssText = convertCssObjectToCssText({
...userStyle,
...transform.toObject(),
//
// width: `${scaleX * dimensions.clientWidth}px`,
// height: `${scaleY * dimensions.clientHeight}px`,
});
});
});
Expand Down
24 changes: 24 additions & 0 deletions src/transition/utils/cubic-bezier.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Vector } from './vector';

/**
* Used to create a cubic bezier easing function. Where to lookup equation https://en.wikipedia.org/wiki/B%C3%A9zier_curve
* Points zero and three are the start and end points. In CSS, these are (0, 0) and (1, 1) respectively.
* Here's a cubic bezier editor for CSS https://cubic-bezier.com/. In CSS, first two values are the first control point and the last two are the second.
* @param controlPointOne
* @param controlPointTwo
* @returns a cubic bezier easing function
*/
export function cubicBezier(controlPointOne: Vector, controlPointTwo: Vector) {
/**
* @param t 0 <= t <= 1
*/
return function ease(t: number) {
// first term drops out as P_0 is (0, 0)
const secondTerm = controlPointOne.multiply(3 * t * (1 - t) ** 2);
const thirdTerm = controlPointTwo.multiply(3 * (1 - t) * t ** 2);
const fourthTerm = new Vector(1, 1).multiply(t ** 3);
const position = secondTerm.add(thirdTerm).add(fourthTerm);
// returning y component
return position.get(1);
}
}
19 changes: 17 additions & 2 deletions src/transition/utils/easing.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { cubicBezier } from './cubic-bezier';
import { Vector } from './vector';

export type Easing = {
start: () => void;
stop: () => void;
Expand Down Expand Up @@ -31,6 +34,16 @@ type time = number;
type onStart = () => void;
type during = (delta: number) => void;
type onEnd = () => void;
type EaseFunction = (t: number) => number;

// From MDN: cubic-bezier(0.0, 0.0, 0.58, 1.0) https://developer.mozilla.org/en-US/docs/Web/CSS/easing-function
const easeOut = cubicBezier(new Vector(0, 0), new Vector(0.58, 1.0));
// From MDN: cubic-bezier(0.42, 0.0, 1.0, 1.0) https://developer.mozilla.org/en-US/docs/Web/CSS/easing-function
const easeIn = cubicBezier(new Vector(0.42, 0), new Vector(1.0, 1.0));
// From MDN: cubic-bezier(0.42, 0.0, 0.58, 1.0) https://developer.mozilla.org/en-US/docs/Web/CSS/easing-function
const easeInOut = cubicBezier(new Vector(0.42, 0), new Vector(0.58, 1.0));
// Uses sin curve to ease out as d/dx of sin(x) is cos(x) so rate of change slows as x -> pi/2
const sinEaseOut: EaseFunction = x => Math.sin((x * Math.PI) / 2);

class Ease {
time: time;
Expand All @@ -40,15 +53,17 @@ class Ease {
hasStarted: boolean;
stopped: boolean;
firstTimestamp: number;
easeFunction: EaseFunction;

constructor(time: number, onStart: onStart, during: during, onEnd: onEnd) {
constructor(time: number, onStart: onStart, during: during, onEnd: onEnd, easeFunction: EaseFunction = sinEaseOut) {
this.time = time;
this.onStart = onStart;
this.during = during;
this.onEnd = onEnd;
this.hasStarted = false;
this.stopped = false;
this.firstTimestamp = 0;
this.easeFunction = easeFunction;
}

run(timestamp: number) {
Expand All @@ -63,7 +78,7 @@ class Ease {
}

const progress = timestamp - this.firstTimestamp;
const delta = (x => Math.sin((x * Math.PI) / 2))(progress / this.time);
const delta = this.easeFunction(progress / this.time);

if (progress < this.time) {
this.during(delta);
Expand Down
54 changes: 54 additions & 0 deletions src/transition/utils/vector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@

/**
* Class that supports basic Vector math
*/
export class Vector {
#values: number[];

constructor(...values: number[]) {
this.#values = values;
}

multiply(scalar: number) {
return new Vector(...this.#values.map(v => v * scalar));
}

add(vector: Vector) {
this.#assertSameLength(vector);
return new Vector(...this.#values.map((v, i) => v + vector.#values[i]));
}

dot(vector: Vector) {
this.#assertSameLength(vector);
return this.#values.reduce((acc, curr, i) => acc + curr * vector.#values[i], 0);
}

get(index: number) {
return this.#values[index];
}

#assertSameLength(vector: Vector) {
assert(vector.#values.length, this.#values.length, 'The lengths of the vectors mismatch.');
}
}

function assert(value1: any, value2: any, message?: string) {
if (value1 !== value2) {
throw new Error(message);
}
}

export function test() {
// Testing dot product
assert((new Vector(1, 0)).dot(new Vector(0, 1)), 0, 'Failed dot product');
assert((new Vector(1, 1)).dot(new Vector(0, 1)), 1, 'Failed dot product');
assert((new Vector(2, 1)).dot(new Vector(1, 3)), 5, 'Failed dot product');
// Testing addition
const resultAdd = (new Vector(4, 1)).add(new Vector(3, 2));
assert(resultAdd.get(0), 7, 'Failed addition');
assert(resultAdd.get(1), 3, 'Failed addition');
// Testing multiplying by a scalar
const resultMultiplication = (new Vector(4, 1)).multiply(5);
assert(resultMultiplication.get(0), 20, 'Failed scalar multiplication');
assert(resultMultiplication.get(1), 5, 'Failed scalar multiplication');
}