diff --git a/src/App.css b/src/App.css new file mode 100644 index 0000000..bbe1d76 --- /dev/null +++ b/src/App.css @@ -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; +} diff --git a/src/App.tsx b/src/App.tsx index de6013c..89d4597 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -15,10 +15,17 @@ function App() { - - - 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. - + +
+ +
hey
+
what
+
+
+ Hello World + + There is a bunch of text +
@@ -27,7 +34,7 @@ function App() { > {bool &&
HELLO
} - {!bool &&
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.
} + {!bool &&
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.
}
diff --git a/src/stories/Transition.tsx b/src/stories/Transition.tsx index 9b7c361..22a89a0 100644 --- a/src/stories/Transition.tsx +++ b/src/stories/Transition.tsx @@ -1,8 +1,9 @@ import React from 'react'; import { Transition } from '../transition'; +import App from '../App'; export const TransitionStory = () => ( - Story Skeleton + ); diff --git a/src/transition/transition.tsx b/src/transition/transition.tsx index 703d5ee..6a430e0 100644 --- a/src/transition/transition.tsx +++ b/src/transition/transition.tsx @@ -89,7 +89,7 @@ export class Transition extends Component { 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; @@ -174,9 +174,21 @@ export class Transition extends Component { .forEach(([selector, transform]) => { this._containerRef.current?.querySelectorAll(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`, }); }); }); diff --git a/src/transition/utils/cubic-bezier.ts b/src/transition/utils/cubic-bezier.ts new file mode 100644 index 0000000..8fd32a0 --- /dev/null +++ b/src/transition/utils/cubic-bezier.ts @@ -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); + } +} diff --git a/src/transition/utils/easing.ts b/src/transition/utils/easing.ts index 9d05709..c50461e 100644 --- a/src/transition/utils/easing.ts +++ b/src/transition/utils/easing.ts @@ -1,3 +1,6 @@ +import { cubicBezier } from './cubic-bezier'; +import { Vector } from './vector'; + export type Easing = { start: () => void; stop: () => void; @@ -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; @@ -40,8 +53,9 @@ 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; @@ -49,6 +63,7 @@ class Ease { this.hasStarted = false; this.stopped = false; this.firstTimestamp = 0; + this.easeFunction = easeFunction; } run(timestamp: number) { @@ -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); diff --git a/src/transition/utils/vector.ts b/src/transition/utils/vector.ts new file mode 100644 index 0000000..ee20be3 --- /dev/null +++ b/src/transition/utils/vector.ts @@ -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'); +}