Skip to content

Commit

Permalink
Add parallax animation in device screen (#34)
Browse files Browse the repository at this point in the history
* initial commit
  • Loading branch information
evertonstz authored Mar 21, 2024
1 parent 8240754 commit 5ba4899
Show file tree
Hide file tree
Showing 10 changed files with 259 additions and 49 deletions.
9 changes: 8 additions & 1 deletion src/components/AddDeviceFooter/AddDeviceFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ import SerialInput from './SerialInput';
const SERIAL_SIZE = 8;
const INPUT_PLACEHOLDER = `Insert ${SERIAL_SIZE} digits serial`;

interface Props {
onFocus?: React.FocusEventHandler<HTMLInputElement>;
onBlur?: React.FocusEventHandler<HTMLInputElement>;
}

const buildOptions = (devices: Pax.lib.Devices[]) => {
return devices.map(device => {
return { value: device, label: device };
Expand All @@ -23,7 +28,7 @@ const addNewDeviceButtonDisabled = (serialInput: string | undefined) => {
return false;
};

const AddDeviceFooter = () => {
const AddDeviceFooter = (props: Props) => {
const defaultDevice = SUPPORTED_DEVICES[0];
const [serialInput, setSerialInput] = useState<string | undefined>(undefined);
const [deviceValue, setDeviceValue] =
Expand Down Expand Up @@ -56,6 +61,8 @@ const AddDeviceFooter = () => {
value={serialInput}
onValueChange={setSerialInput}
placeholder={INPUT_PLACEHOLDER}
onFocus={props.onFocus}
onBlur={props.onBlur}
/>
</div>
<Button
Expand Down
14 changes: 6 additions & 8 deletions src/components/AddDeviceFooter/SerialInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ interface Props {
onValueChange?: (input: string) => void;
max?: number;
placeholder?: string;
onFocus?: React.FocusEventHandler<HTMLInputElement>;
onBlur?: React.FocusEventHandler<HTMLInputElement>;
}
const SerialInput = ({ value, onValueChange, max, placeholder }: Props) => {
const SerialInput = (props: Props) => {
const { value, onValueChange, max, placeholder, onFocus, onBlur } = props;
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (!onValueChange) {
return;
Expand All @@ -31,6 +34,8 @@ const SerialInput = ({ value, onValueChange, max, placeholder }: Props) => {
onChange={e => handleChange(e)}
value={value}
placeholder={placeholder}
onFocus={onFocus}
onBlur={onBlur}
/>
<div className="absolute inset-y-0 right-0 mr-[1px] flex items-center">
<p
Expand All @@ -42,13 +47,6 @@ const SerialInput = ({ value, onValueChange, max, placeholder }: Props) => {
</p>
</div>
</div>
// <Input
// className={`rounded-none focus-visible:border-neutral-400 focus-visible:ring-0
// focus-visible:ring-offset-0 dark:focus-visible:border-neutral-600`}
// onChange={e => handleChange(e)}
// value={value}
// placeholder={placeholder}
// />
);
};
export default SerialInput;
151 changes: 151 additions & 0 deletions src/components/Graphics/PaxPairingSvg/ParallaxComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import React, { useEffect, useState } from 'react';

interface ParallaxComponentProps {
children: React.ReactNode;
multiplier?: number;
}

interface MouseCoordnates {
x: {
current: number;
max: number;
min: number;
perc: number;
};
y: {
current: number;
max: number;
min: number;
perc: number;
};
}

const mouseCoordnatesInitialValue: MouseCoordnates = {
x: {
current: 0,
max: 0,
min: 0,
perc: 0,
},
y: {
current: 0,
max: 0,
min: 0,
perc: 0,
},
};

/**
* Calculate the y-coordinate in a two-dimensional coordinate system based on the given
* x-coordinate and a percentage between initial and final x-coordinates.
*
* @param {Object} coordinates - An object containing initial and final x and y coordinates
* @param {number} coordinates.x.initial - The initial x-coordinate
* @param {number} coordinates.x.final - The final x-coordinate
* @param {number} coordinates.y.initial - The initial y-coordinate
* @param {number} coordinates.y.final - The final y-coordinate
* @param {number} currentPercentage - The percentage between initial and final x-coordinates
* @return {number} The calculated y-coordinate
*/
const centerCoordinateSystem = (
coordinates: {
x: { initial: number; final: number };
y: { initial: number; final: number };
},
currentPercentage: number,
) => {
const m =
(coordinates.y.final - coordinates.y.initial) /
(coordinates.x.final - coordinates.x.initial);
const y =
m * (currentPercentage - coordinates.x.initial) + coordinates.y.initial;
return y;
};

/**
* Translate the current percentage to center coordinate system.
*
* @param {number} currentPercentage - the current percentage value to be translated
* @return {number} the calculated value in the center coordinate system
*/
const translate = (currentPercentage: number) => {
if (currentPercentage > 0.5) {
// 0.51 to 1
const calculated = centerCoordinateSystem(
{
x: {
initial: 0.51,
final: 1,
},
y: {
initial: 0,
final: 1,
},
},
currentPercentage,
);

return calculated;
} else {
// 0 to 0.5
const calculated = centerCoordinateSystem(
{
x: {
initial: 0,
final: 0.5,
},
y: {
initial: -1,
final: 0,
},
},
currentPercentage,
);
return calculated;
}
};

const ParallaxComponent = (props: ParallaxComponentProps) => {
const { children, multiplier = 10 } = props;
const [mouseCoord, setMouseCoord] = useState<MouseCoordnates>(
mouseCoordnatesInitialValue,
);

useEffect(() => {
const handleMouseMove = (event: MouseEvent) => {
setMouseCoord({
x: {
current: event.clientX,
max: window.innerWidth - 1,
min: 0,
perc: event.clientX / window.innerWidth,
},
y: {
current: event.clientY,
max: window.innerHeight - 1,
min: 0,
perc: event.clientY / window.innerHeight,
},
});
};

window.addEventListener('mousemove', handleMouseMove);

return () => {
window.removeEventListener('mousemove', handleMouseMove);
};
}, []);

const parallaxStyle: React.CSSProperties = {
transform: `translate(${-translate(mouseCoord.x.perc) * multiplier}px,
${-translate(mouseCoord.y.perc) * multiplier}px)`,
};

return (
<div className="h-full w-full" style={parallaxStyle}>
{children}
</div>
);
};

export default ParallaxComponent;
61 changes: 38 additions & 23 deletions src/components/Graphics/PaxPairingSvg/PaxPairing.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,49 @@
import ParallaxComponent from './ParallaxComponent';
import PulsatingLight from './PulsatingLight';
import Pax3DeviceSvg from './Svg/Pax3DeviceSvg';

const PaxPairing = () => {
interface Props {
parallax?: boolean;
pulsatingLightSpeed?: 'slow' | 'normal' | 'fast';
}

const PaxPairing = (props: Props) => {
const svgFillColor = '#666666';
const lightColor = '#00FFFF';

const renderWithParallax = (
multiplier: number,
children: React.ReactNode,
parallax?: boolean,
) => {
if (!parallax) {
return children;
}

return (
<ParallaxComponent multiplier={multiplier}>{children}</ParallaxComponent>
);
};

return (
<div
style={{
position: 'relative',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
<div
style={{
position: 'absolute',
top: 60,
}}
>
<PulsatingLight radius={'5px'} color={lightColor} />
<div className="relative flex h-72 items-center justify-center">
<div className="absolute top-14"></div>
{renderWithParallax(
7,
<Pax3DeviceSvg fillColor={svgFillColor} showShadow />,
props.parallax,
)}
<div className="absolute top-12">
{renderWithParallax(
10,
<PulsatingLight
radius={'5px'}
color={lightColor}
speed={props.pulsatingLightSpeed}
/>,
props.parallax,
)}
</div>
<Pax3DeviceSvg
fillColor={svgFillColor}
showShadow
style={{
width: '50%',
}}
/>
</div>
);
};
Expand Down
22 changes: 18 additions & 4 deletions src/components/Graphics/PaxPairingSvg/PulsatingLight.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,38 @@ import React, { useEffect, useState } from 'react';
interface PulsatingLightProps {
radius?: string;
color: string;
speed?: 'slow' | 'normal' | 'fast';
}

const PulsatingLight = ({ radius, color }: PulsatingLightProps) => {
const PulsatingLight = (props: PulsatingLightProps) => {
const { speed = 'normal', color, radius } = props;
const [isBloomed, setIsBloomed] = useState(false);
const getSpeed = (speed: 'fast' | 'normal' | 'slow') => {
switch (speed) {
case 'fast':
return 0.5;
case 'slow':
return 1.5;
default:
return 1;
}
};
const speedInSeconds = getSpeed(speed);

useEffect(() => {
const interval = setInterval(() => {
setIsBloomed(prevState => !prevState);
}, 1000);
}, speedInSeconds * 1000);
return () => clearInterval(interval);
}, []);
}, [speedInSeconds]);

const animationStyle: React.CSSProperties = {
width: radius ? radius : '100px',
height: radius ? radius : '100px',
borderRadius: '50%',
backgroundColor: color,
boxShadow: isBloomed ? `0 0 50px 10px ${color}` : 'none',
transition: 'box-shadow 1s ease-in-out',
transition: `box-shadow ${speedInSeconds}s ease-in-out`,
};

const buildCircle = (x: string, y: string) => {
Expand Down
10 changes: 4 additions & 6 deletions src/components/Graphics/PaxPairingSvg/Svg/Pax3DeviceSvg.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,15 @@ interface Pax3DeviceSvgProps {
showShadow?: boolean;
}

const Pax3DeviceSvg = ({
fillColor,
style,
showShadow,
}: Pax3DeviceSvgProps) => {
const Pax3DeviceSvg = (props: Pax3DeviceSvgProps) => {
const { fillColor, style, showShadow } = props;
return (
<svg
id="pax3device"
viewBox="0 0 339 675"
fill="none"
xmlns="http://www.w3.org/2000/svg"
style={{ width: '100%', display: 'block', ...style }}
style={{ display: 'block', width: '100%', height: '100%', ...style }}
>
<rect width="100%" height="100%" />
<path
Expand Down
15 changes: 13 additions & 2 deletions src/components/MainContent/NoSelectedDevice.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
import { useIsMobile } from '@/hooks';
import { useState } from 'react';

import AddDeviceFooter from '../AddDeviceFooter';
import { PaxPairing } from '../Graphics';

export const NoSelectedDevice = () => {
const isMobile = useIsMobile();
const [isInputOnFocus, setIsInputOnFocus] = useState(false);
return (
<div className="mx-auto flex flex-col gap-6 self-center">
<PaxPairing />
<AddDeviceFooter />
<PaxPairing
parallax={!isMobile}
pulsatingLightSpeed={isInputOnFocus ? 'fast' : 'slow'}
/>
<AddDeviceFooter
onFocus={() => setIsInputOnFocus(true)}
onBlur={() => setIsInputOnFocus(false)}
/>
</div>
);
};
Loading

0 comments on commit 5ba4899

Please sign in to comment.