Skip to content

Commit

Permalink
Merge pull request #42 from softeerbootcamp4th/feature/21-universalIs…
Browse files Browse the repository at this point in the history
…landInteraction

[feat] 유니버설 아일랜드 인터랙션 추가 (resolve #21)
  • Loading branch information
darkdulgi authored Jul 29, 2024
2 parents e903158 + 047ca76 commit f88f238
Show file tree
Hide file tree
Showing 12 changed files with 364 additions and 39 deletions.
32 changes: 32 additions & 0 deletions src/common/useMountDragEvent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useEffect } from "react";
import throttleRaf from "@/common/throttleRaf.js";

function useMountDragEvent(dragging, dragEnd) {
useEffect(() => {
const onPointerMove = throttleRaf((e) => {
const { clientX, clientY } = e;
dragging({ x: clientX, y: clientY });
});
const onTouchMove = throttleRaf((e) => {
const { clientX, clientY } = e.touches[0];
dragging({ x: clientX, y: clientY });
});

window.addEventListener("pointermove", onPointerMove);
window.addEventListener("pointerup", dragEnd);
window.addEventListener("pointercancel", dragEnd);
window.addEventListener("touchmove", onTouchMove);
window.addEventListener("touchend", dragEnd);
window.addEventListener("touchcancel", dragEnd);
return () => {
window.removeEventListener("pointermove", onPointerMove);
window.removeEventListener("pointerup", dragEnd);
window.removeEventListener("pointercancel", dragEnd);
window.removeEventListener("touchmove", onTouchMove);
window.removeEventListener("touchend", dragEnd);
window.removeEventListener("touchcancel", dragEnd);
};
}, [dragging, dragEnd]);
}

export default useMountDragEvent;
2 changes: 1 addition & 1 deletion src/interactions/fastCharge/FastChargeInteraction.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function FastChargeInteraction({ interactCallback, $ref }) {
const progress = getProgress(angle);

return (
<article className="bg-black relative w-full h-full overflow-hidden flex items-center flex-col">
<article className="relative w-full h-full overflow-hidden flex items-center flex-col">
<div className="w-full max-w-[1200px] px-10 lg:px-20 flex gap-2 items-start mt-16 lg:mt-[6.25rem] ">
<img src={orderIcon} alt="2" />
<div className="flex flex-col gap-3.5 font-bold">
Expand Down
52 changes: 14 additions & 38 deletions src/interactions/fastCharge/useDialDrag.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState, useRef, useCallback, useEffect } from "react";
import { useState, useRef, useCallback } from "react";
import { clamp } from "@/common/utils.js";
import throttleRaf from "@/common/throttleRaf.js";
import useMountDragEvent from "@/common/useMountDragEvent.js";

function getAngle(pointer, center) {
const vx = pointer.x - center.x;
Expand All @@ -24,46 +24,22 @@ function useDialDrag() {
const prevAngle = useRef(0);
const angleCache = useRef(0);

useEffect(() => {
function applyPointerMove(cursor) {
const applyPointerMove = useCallback(
(cursor) => {
if (!isDrag) return;
const currentAngle = getAngle(cursor, dialCenter.current);

angleCache.current += getAngleDelta(prevAngle.current, currentAngle);
setAngle(angleCache.current);
prevAngle.current = currentAngle;
}

const onPointerMove = throttleRaf((e) => {
if (!isDrag) return;
const { clientX, clientY } = e;
applyPointerMove({ x: clientX, y: clientY });
});
const onTouchMove = throttleRaf((e) => {
if (!isDrag) return;
const { clientX, clientY } = e.touches[0];
applyPointerMove({ x: clientX, y: clientY });
});
function onPointerEnd() {
setIsDrag(false);
angleCache.current = clamp(angleCache.current, -Math.PI * 2, 0);
setAngle(angleCache.current);
}

window.addEventListener("pointermove", onPointerMove);
window.addEventListener("pointerup", onPointerEnd);
window.addEventListener("pointercancel", onPointerEnd);
window.addEventListener("touchmove", onTouchMove);
window.addEventListener("touchend", onPointerEnd);
window.addEventListener("touchcancel", onPointerEnd);
return () => {
window.removeEventListener("pointermove", onPointerMove);
window.removeEventListener("pointerup", onPointerEnd);
window.removeEventListener("pointercancel", onPointerEnd);
window.removeEventListener("touchmove", onTouchMove);
window.removeEventListener("touchend", onPointerEnd);
window.removeEventListener("touchcancel", onPointerEnd);
};
}, [isDrag]);
},
[isDrag],
);
const onPointerEnd = useCallback(() => {
setIsDrag(false);
angleCache.current = clamp(angleCache.current, -Math.PI * 2, 0);
setAngle(angleCache.current);
}, []);
useMountDragEvent(applyPointerMove, onPointerEnd);

function onPointerStart(e) {
if (dialRef.current === null) return;
Expand Down
58 changes: 58 additions & 0 deletions src/interactions/univasalIsland/Phone.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
function Phone({ dynamicStyle, onPointerDown, isSnapped }) {
const staticStyle = `absolute flex justify-center items-center
left-[541px] top-[293px] w-[54px] h-[97px]
lg:left-[528px] lg:top-[185px] lg:w-[66px] lg:h-[118px]
xl:left-[516px] xl:top-[75px] xl:w-[77px] xl:h-[140px]
touch-none
`;
const phoneScreenFill = isSnapped ? "fill-green-700" : "fill-neutral-900";
const lightningOpacity = isSnapped ? "opacity-100" : "opacity-0";

return (
<div
className={staticStyle}
style={dynamicStyle}
onPointerDown={onPointerDown}
>
<svg
className="w-full h-full absolute top-0 left-0"
width="66"
height="127"
viewBox="0 0 63 127"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect
x="-1.5"
y="1.5"
width="66"
height="124"
rx="10.5"
fill="#181818"
stroke="#97E0FF"
strokeWidth="3"
className={`${phoneScreenFill} transition-colors`}
/>
<path
d="M17 2H45V5C45 6.65685 43.6569 8 42 8H20C18.3431 8 17 6.65685 17 5V2Z"
fill="#97E0FF"
/>
</svg>
<svg
width="15"
height="28"
viewBox="0 0 15 28"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={`${lightningOpacity} z-10 transition-colors`}
>
<path
d="M10.6239 13.1582C10.266 12.9321 10.0893 12.5051 10.1826 12.0922L12.5679 1.54338C12.792 0.552496 11.567 -0.102448 10.8675 0.634189L0.838941 11.1938C0.404727 11.651 0.496867 12.391 1.02993 12.7278L4.37611 14.8418C4.734 15.0679 4.91075 15.4949 4.81738 15.9078L2.43207 26.4566C2.20801 27.4475 3.43296 28.1024 4.13255 27.3658L14.1611 16.8062C14.5953 16.349 14.5031 15.609 13.9701 15.2722L10.6239 13.1582Z"
fill="#BBFBF0"
/>
</svg>
</div>
);
}

export default Phone;
108 changes: 108 additions & 0 deletions src/interactions/univasalIsland/UnivasalIslandInteraction.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { useImperativeHandle } from "react";
import Phone from "./Phone.jsx";
import useIslandDrag from "./useIslandDrag.js";

import orderIcon from "@/assets/property3.svg";
import seat from "./assets/seat.png";
import univasalIsland1x from "./assets/[email protected]";
import univasalIsland2x from "./assets/[email protected]";
import univasalIslandLeg from "./assets/univasalIsland2.png";

function UnivasalIslandInteraction({ interactCallback, $ref }) {
const {
islandEventListener,
phoneEventListener,
islandStyle,
phoneStyle,
reset,
phoneSnapArea,
phoneIsSnapping,
} = useIslandDrag();

useImperativeHandle($ref, () => ({ reset }), [reset]);

const seatHullStyle = `absolute w-[1200px] h-[800px]
bottom-[min(calc(100%-800px),-140px)]
lg:bottom-[min(calc(100%-900px),-170px)]
xl:bottom-[min(calc(100%-1000px),-200px)]
flex justify-center items-end select-none`;

const seatStyle = `w-[317.44px] h-[501.88px]
lg:w-[385.46px] lg:h-[610.64px]
xl:w-[453.48px] xl:h-[718.4px]`;

const univasalIslandStaticStyle = `w-[158.2px] h-[546px]
lg:w-[192.1px] lg:h-[663px]
xl:w-[226px] xl:h-[780px]
flex flex-col gap-2 cursor-pointer touch-none`;

const snapAreaStyle = `absolute scale-50
left-[21px] top-[40px] w-[54px] h-[97px]
lg:left-[25px] lg:top-[49px] lg:w-[66px] lg:h-[118px]
xl:left-[30px] xl:top-[56px] xl:w-[77px] xl:h-[140px]
`;

return (
<article className="relative w-full h-full overflow-hidden flex items-center flex-col">
<div className="w-full max-w-[1200px] px-10 lg:px-20 flex gap-2 items-start mt-16 lg:mt-[6.25rem] ">
<img src={orderIcon} alt="3" />
<div className="flex flex-col gap-3.5 font-bold">
<h3 className="text-neutral-400 text-title-m md:text-title-l">
나에게 맞게, 자유자재로
</h3>
<p className="text-white text-body-m md:text-body-l">
새로워진 The new IONIQ 5의 유니버설 아일랜드는 어떤 모습일까요?
</p>
<p className="text-neutral-200 text-body-s">
유니버설 아일랜드를 드래그하여 이동시키고 스마트폰을 충전해보세요!
</p>
</div>
</div>
<div className={seatHullStyle}>
<img
className={seatStyle}
src={seat}
alt="left seat"
draggable="false"
/>
<div
className={univasalIslandStaticStyle}
style={islandStyle}
onPointerDown={(e) => {
islandEventListener.onPointerDown(e);
interactCallback?.();
}}
>
<img
src={univasalIsland1x}
srcSet={`${univasalIsland1x} 1x, ${univasalIsland2x} 2x`}
alt="univasal island"
draggable="false"
/>
<img
src={univasalIslandLeg}
alt="univasal island"
draggable="false"
/>
<div className={snapAreaStyle} ref={phoneSnapArea}></div>
</div>
<img
className={seatStyle}
src={seat}
alt="right seat"
draggable="false"
/>
<Phone
isSnapped={phoneIsSnapping}
dynamicStyle={phoneStyle}
onPointerDown={(e) => {
phoneEventListener.onPointerDown(e);
interactCallback?.();
}}
/>
</div>
</article>
);
}

export default UnivasalIslandInteraction;
3 changes: 3 additions & 0 deletions src/interactions/univasalIsland/assets/chargeMark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/interactions/univasalIsland/assets/iphone.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/interactions/univasalIsland/assets/seat.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit f88f238

Please sign in to comment.