I've been working on a left swipe gesture handler for bubbles in a chat. touchMove
and touchStart
would probably be the next step, but right now I'm trying to get this to work just for PC/web.
I kind of got it working, but I feel like there is a much better way, than just updating the inline transform
style for each bubble. It doesn't feel quite as natural as I'd expect it to.
I've also tried a "hacky" way with scroll-snap
, but I couldn't quite get it to work.
Ideally, I'd love a pure CSS solution, but I'll take what I can get. Is the below approach most likely the only one I can use?
// App.tsx
import * as React from "react";
import "./styles.css";
import { createSwipeHandlers } from "./createSwipeHandlers";
const BubbleRow = React.memo(
({
messageText,
setSelectedMessage,
isLastBubble
}: {
messageText: string;
setSelectedMessage: (messageText: string) => void;
isLastBubble: boolean;
}) => {
const bubbleRef = React.useRef<HTMLDivElement>(null);
const handleLeftSwipe = React.useCallback((dist: number) => {
if (bubbleRef.current) {
if (dist > 72) {
//@ts-expect-error
bubbleRef.current.style.transform = null;
setSelectedMessage(messageText);
return;
}
bubbleRef.current.style.transform = `translateX(-${dist}px)`;
}
}, []);
const { mouseStart, mouseMove, mouseOut, mouseStop } = createSwipeHandlers({
leftSwipeHandler: handleLeftSwipe,
swipeStopHandler: (e: any) => {
if (bubbleRef.current) {
//@ts-expect-error
bubbleRef.current.style.transform = null;
}
}
});
const className = `row${isLastBubble ? " last-bubble-row" : ""}`;
return (
<div className={className}>
<div
ref={bubbleRef}
className={"bubble"}
onMouseDown={mouseStart}
onMouseMove={mouseMove}
onMouseOut={mouseOut}
onMouseLeave={mouseStop}
onMouseUp={mouseStop}
>
{messageText}
</div>
</div>
);
}
);
const Footer = React.memo(
({
selectedMessage,
setSelectedMessage
}: {
selectedMessage: string | null;
setSelectedMessage: (selectedMessage: null) => void;
}) => {
const clearSelectedMessage = React.useCallback(() => {
setSelectedMessage(null);
}, []);
if (!selectedMessage) return null;
return (
<div key={selectedMessage} className="footer">
<span>{selectedMessage}</span>
<span onClick={clearSelectedMessage} className="clear-button">
{"×"}
</span>
</div>
);
}
);
const numberOfChatBubbles = 5;
export const App = () => {
const [selectedMessage, setSelectedMessage] = React.useState<string | null>(
null
);
return (
<div className="wrapper">
<div className="container">
{Array.from({ length: numberOfChatBubbles }, (_, index) => (
<BubbleRow
messageText={`Text for message ${index + 1}`}
setSelectedMessage={setSelectedMessage}
isLastBubble={index + 1 === numberOfChatBubbles}
/>
))}
<Footer
selectedMessage={selectedMessage}
setSelectedMessage={setSelectedMessage}
/>
</div>
</div>
);
};
const createSwipeHandlers = ({
leftSwipeHandler,
swipeStopHandler,
minSwipeDist = 5
}: {
leftSwipeHandler?: (dist: number) => void;
swipeStopHandler?: (e: React.MouseEvent<HTMLDivElement>) => void;
minSwipeDist?: number;
}) => {
let xDown: number | null = null;
let yDown: number | null = null;
return {
mouseStart: ((e: React.MouseEvent<HTMLDivElement>) => {
xDown = e.clientX;
yDown = e.clientY;
}) as React.MouseEventHandler,
mouseMove: ((e: React.MouseEvent<HTMLDivElement>) => {
if (!xDown || !yDown) return;
const xUp = e.clientX;
const yUp = e.clientY;
const xDiff = xDown - xUp;
const xDiffAbs = Math.abs(xDiff);
const yDiff = yDown - yUp;
const yDiffAbs = Math.abs(yDiff);
if (xDiffAbs > yDiffAbs && xDiff > 0 && xDiffAbs > minSwipeDist) {
leftSwipeHandler?.(xDiffAbs);
}
}) as React.MouseEventHandler,
mouseOut: ((e: React.MouseEvent<HTMLDivElement>) => {
xDown = null;
yDown = null;
}) as React.MouseEventHandler,
mouseStop: ((e: React.MouseEvent<HTMLDivElement>) => {
xDown = null;
yDown = null;
swipeStopHandler?.(e);
}) as React.MouseEventHandler
};
};
// styles.css
* {
outline: none;
box-sizing: border-box;
margin: 0;
}
.wrapper {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100vh;
}
.container {
border: 1px solid black;
height: 500px;
width: 400px;
display: flex;
flex-direction: column;
}
.row {
display: flex;
justify-content: flex-end;
width: 100%;
padding: 8px;
}
.bubble {
display: flex;
align-items: center;
background-color: gray;
padding: 12px;
border-radius: 12px 12px 3px 12px;
transition: 0.04s linear;
height: fit-content;
user-select: none;
}
.last-bubble-row {
flex-grow: 1;
}
@keyframes blinkFade {
0%,
100% {
filter: brightness(1);
}
20% {
filter: brightness(0.4);
}
}
.footer {
height: 64px;
width: 100%;
padding: 12px;
display: flex;
align-items: center;
justify-content: space-between;
animation: blinkFade 1.5s;
background-color: gray;
}
.clear-button {
cursor: pointer;
font-size: 32px;
}
Currently looks like this:
from Is there a better way to recognize and update the in-line style of an element during a swipe gesture?
No comments:
Post a Comment