How do I set element to display where my mouse clicks? (React) - javascript

I have some code below which shows/hides an li element when I click on an image. The code below works as intended; however, the image I'm using fits the entire screen and currently when the li element is shown it displays on the very top left of the image. I want the li element to display exactly where I'm clicking but having difficulty figuring out how I can do that. Please advice.
GamePage.js
const GamePage = () => {
const [isShown, setIsShown] = useState(false);
const handleClick = () => {
setIsShown((current) => !current);
};
return (
<div className="gamepage" onClick={handleClick}>
{isShown && (
<ul className="menu">
<li className="menu-item">Waldo</li>
</ul>
)}
<GamePageImage />
</div>
);
};
GamePage.css
.gamepage {
position: relative;
}
.menu {
position: absolute;
text-align: center;
list-style-type: none;
margin: 5px;
padding: 0;
border: 1px solid #3e3e3e;
width: 100px;
}
.menu > li {
margin: 0;
color: white;
background-color: #3e3e3e;
}
.menu > li:hover {
cursor: pointer;
background-color: #252525;
}

The onClick fires an event, where you can get the x and y coordinates.
Save that position and use it on your ul element to place it where you want it.
I've added some CSS to ul which will place it exactly where you click.
const GamePage = () => {
const [isShown, setIsShown] = useState(false);
const [position, setPosition] = useState([0,0]) // State to save the position where you clicked
const handleClick = (event) => {
setIsShown(current => !current)
setPosition([event.pageX,event.pageY]) // Save the pos where you clicked
}
return (
<div className="gamepage" onClick={handleClick}>
{isShown && (
<ul className="menu" style={{
position: "absolute",
left: position[0],
top: position[1],
tranform: "translateX(-50%)",
transform: "translateY(-50%)",
}}>
<li className="menu-item">Waldo</li>
</ul>
)}
<GamePageImage />
</div>
);
};

Related

Switch active buttons using useState and useEffect Hooks for Buttons in JavaScript/React?

I am trying to write a useState() Hook, and perhaps add useEffect() to solve active state on two buttons. It is Delivery buttons that needs the first button-delivery to be active using CSS change, and if clicked on second button, PickUp, it should change CSS UI to stay active.
And yes if it is anyhow possible i want to use Hooks.
Is there any possible way to have it done on this way?
const Header = props => {
const [isActive, setIsActive] = useState(false);
function changeButtons () {
setIsActive = (!isActive)
};
return (
<Fragment>
<header className={classes.header}>
<div className={classes.logo} onClick={reload}>
<div >
Foodzilla
</div>
</div>
<div className={classes.delivery}>
<div
className={isActive ? classes.deliveryAction : classes.deliveryChoice}
onChange={changeButtons}
>Delivery</div>
<div className={classes.or}>or</div>
<div
className={isActive ? classes.pickUpAction : classes.pickUpChoice}
onChange={changeButtons}
>Pick Up</div>
</div>
Okay, so I did a mockup of what you are trying to do (I think :D). Here is a link to the working solution: https://codesandbox.io/s/quiet-mountain-68e10k?file=/src/styles.css:59-775.
The code is also below. There is definitely some refactoring that can be done, but I wanted to at least get you started on the right path.
Quick Summary:
Blue is Cicked (takes precedence over hover and default when active).
Green is Hovered (goes back to default when outside the div).
Red is Default (if not clicked or hovered, show red).
import "./styles.css";
import React, { useState } from "react";
export default function App() {
const [isDeliveryClicked, setIsDeliveryClicked] = useState(false);
const [isPickupClicked, setIsPickupClicked] = useState(false);
const [isDeliveryHovered, setIsDeliveryHovered] = useState(false);
const [isPickupHovered, setIsPickupHovered] = useState(false);
const handleClick = (e) => {
if (e.target.className.includes("delivery")) {
setIsDeliveryClicked(true);
setIsDeliveryHovered(false);
if (isDeliveryClicked === true) {
setIsDeliveryClicked(false);
setIsDeliveryHovered(true)
}
} else if (e.target.className.includes("pickup")) {
setIsPickupClicked(true);
setIsPickupHovered(false);
if (isPickupClicked === true) {
setIsPickupClicked(false);
setIsPickupHovered(true)
}
}
};
const handleOnMouseOver = (e) => {
if (e.target.className.includes("delivery")) {
setIsDeliveryHovered(true);
} else if (e.target.className.includes("pickup")) {
setIsPickupHovered(true);
}
};
const handleOnMouseLeave = (e) => {
if (e.target.className.includes("delivery")) {
setIsDeliveryHovered(false);
} else if (e.target.className.includes("pickup")) {
setIsPickupHovered(false);
}
};
const handleClassStyler = (buttonType) => {
if (buttonType === 'delivery') {
if (isDeliveryClicked === true) {
return "deliveryClicked";
} else if (isDeliveryHovered === true && isDeliveryClicked === false) {
return "deliveryHovered";
} else if (isDeliveryClicked === false && isDeliveryHovered === false) {
return "delivery";
}
} else if (buttonType === 'pickup'){
if (isPickupClicked === true) {
return "pickupClicked";
} else if (isPickupHovered === true && isPickupClicked === false) {
return "pickupHovered";
} else if (isPickupClicked === false && isPickupHovered === false) {
return "pickup";
}
}
};
return (
<div className="App">
<div
onMouseOver={handleOnMouseOver}
onMouseLeave={handleOnMouseLeave}
onClick={handleClick}
className={handleClassStyler('delivery)}
>
Delivery
</div>
<div
onMouseOver={handleOnMouseOver}
onMouseLeave={handleOnMouseLeave}
onClick={handleClick}
className={handlePickupClassStyler('pickup')}
>
Pick Up
</div>
</div>
);
}
The CSS I used for above is:
.delivery {
width: 100px;
height: 100px;
background-color: red;
border: solid black 5px;
margin: 5px;
}
.deliveryClicked {
width: 100px;
height: 100px;
background-color: blue;
border: solid black 5px;
margin: 5px;
}
.deliveryHovered {
width: 100px;
height: 100px;
background-color: green;
border: solid black 5px;
margin: 5px;
}
.pickup {
width: 100px;
height: 100px;
background-color: red;
border: solid black 5px;
margin: 5px;
}
.pickupClicked {
width: 100px;
height: 100px;
background-color: blue;
border: solid black 5px;
margin: 5px;
}
.pickupHovered {
width: 100px;
height: 100px;
background-color: green;
border: solid black 5px;
margin: 5px;
}
Well,
After reading all the input (which was incredibly helpful to get my logic straight) i have come with an idea and some refactoring:
However, as almost close to a solution i still need to solve my default state. And i am stuck again.
Default state should be Delivery, and the (button) should have active CSS as well.
Also, when i add CSS .deliveryChoice:hover {} it does not respond. My guess is that, as it is a child component the header don't respond as it reads no inside buttons.
Right now, they are both off.
My Header component:
const Header = props => {
return (
<Fragment>
<header className={classes.header}>
<div className={classes.logo} onClick={reload}>
<div >
Foodzilla
</div>
</div>
<div className={classes.delivery}>
<DeliveryButton
className={classes.deliveryChoice}
/>
</div>
<div>
<div className={classes.deliveryAdress}>
Delivery to:
</div>
</div>
<div className={classes.deliveryTime}>
<div >
Choose time: Now
</div>
</div>
<HeaderCartButton onClick={props.onShowCart} />
</header>
<div className={classes['main-image']}>
<img src={mealsImg} />
</div>
</Fragment>
And my DeliveryButton:
const deliveryChoice = [{ name: 'Delivery' }, { name: 'PickUp' }]
const DeliveryButton = () => {
const [active, setActive] = useState(false);
return deliveryChoice.map((data, k) => (
<div
key={k}
className={`deliveryChoice ${active === k ?
classes.deliveryAction : ''}`}
onClick={() => setActive(k)}
>
{data.name}
</div>
));
};
And CSS for the Button:
.deliveryChoice {
}
.deliveryAction {
background-color: #ffffff;
border-color: #ffffff;
display: flex;
font-size: 13px;
justify-content: space-around;
width: 4rem;
height: 1.8rem;
cursor: pointer;
color: rgb(0, 0, 0);
align-items: center;
border-radius: 20px;
/* padding-left: 0.5rem; */
}

Adding and removing active class from elements

I'm trying to add and remove active class when I click on different panels that triggers a transition, so if I click on different panels it works, as in it triggers the transition and then it ends it when other panel gets clicked, but if I want to click on a panel that was already opened and closed it won't trigger it again on the first click adn that's not good UX.
I'm writing it in React and I am a beginner so maybe I'm not doing something right.
You can see the code below, I hope I gave all the right information.
componentDidMount() {
ReactDom.findDOMNode(this).addEventListener("transitionend", (e) => {
if (
e.propertyName.includes("flex") &&
e.target.classList.contains("open")
) {
e.target.classList.add("open-active");
}
});
ReactDom.findDOMNode(this).addEventListener("click", (e) => {
const elems = document.querySelector(".open-active");
if (elems !== null) {
elems.classList.remove("open-active", "open", "opac");
}
e.target.className = "open-active";
console.log(e);
});
}
render() {
const { index, top, bottom, image, open, onClick } = this.props;
const style = {
backgroundImage: `url(${image})`,
};
const openValue = open ? "open opac" : "";
return (
<div
className={`panel ${openValue}`}
style={style}
onClick={() => {
onClick(index);
}}
>
</div>
And the CSS
.panel > * {
margin: 0;
width: 100%;
transition: transform 0.5s;
flex: 1 0 auto;
display: flex;
justify-content: center;
align-items: center;
}
.panel > *:first-child {
transform: translateY(-100%);
}
.panel.open-active > *:first-child {
transform: translateY(0);
}
.panel > *:last-child {
transform: translateY(100%);
}
.panel.open-active > *:last-child {
transform: translateY(0);
}
.panel p:nth-child(2) {
font-size: 4em;
}
.panel.open {
font-size: 16px;
flex: 5;
}
Hi you can follow this example:
import React, {useState} from "react";
import './styles/style.css'
export default function ShowHideExample() {
const [cssClass, setCssClass] = useState('hide');
return (
<div className="App">
<h2>Show or Hide div</h2>
<button onClick={() => {
(cssClass === 'hide')? setCssClass('show') : setCssClass('hide');
}}>Click me to show or hide the div
</button>
<div className={cssClass}>
<h1>This is dynamically shown</h1>
</div>
</div>
);
}
Here is the style.css file
.show{
display: block;
background: dodgerblue;
padding:20px;
}
.hide{
display: none;
}

Add styles to button when is active and remove them when clicked again

Im working with react and Draft.js in a plataform with meetings, forums, etc. This editor allows the user to take notes during the meetings. What i want to achieve is that when the user clicks 'Italic' add the .active class to let know the user that the button is active (change the background color) And remove it when is not. This is my code:
export const ActionButton = styled.div`
color: #272a2d;
padding: 3px 7px;
margin-top: 13px;
.active {
background-color: pink
}
&:hover {
background-color: #f2f4f6;
border-radius: 8px;
cursor: pointer;
}
.icon-toolbar-custom-icons {
border: none;
border-radius: 8px;
padding: 5px;
&:hover {
background-color: #f2f4f6;
}
${mediaQuery} {
padding: 0;
}
}
`;
const estilosTooltipInfo = makeStyles(theme => ({
arrow: {
color: theme.palette.common.black,
},
tooltip: {
backgroundColor: theme.palette.common.black,
fontSize: '13px',
padding: '8px 10px',
borderRadius: 6,
},
}));
function TooltipInfo(props) {
const classes = estilosTooltipInfo();
return <Tooltip placement="bottom" classes={classes} {...props} />;
}
function TooltipItalic(props) {
const handleSetItalic = () => {
const newState = RichUtils.toggleInlineStyle(props.editorState, 'ITALIC');
if (newState) {
props.onChange(newState);
}
};
return (
<div>
<TooltipInfo title="Cursiva">
<ActionButton
className="icon-toolbar-custom-icons"
onClick={handleSetItalic}
>
<FormatItalicIcon />
</ActionButton>
</TooltipInfo>
</div>
);
}
i don't know how to achieve this in my onClick method. I know it should be easy but i'm having a hard time here.
It's pretty easy. What you need is getCurrentLinlineStyle of EditorState. It holds current (that you've selected) inline styles. You could see the link with the example below.
Codesanbox example
use onMouseDown instead of onclick event will do that,
export default function MyEditor() {
const [editorState, setEditorState] = React.useState(
() => EditorState.createEmpty(),
);
const _onBoldClick = () => {
setEditorState(RichUtils.toggleInlineStyle(editorState, 'BOLD'))
}
return(
<div>
<button
// onClick={_onBoldClick}
onMouseDown={e=> {
e.preventDefault();
setEditorState(RichUtils.toggleInlineStyle(editorState, 'BOLD'))
}}>BOLD</button>
<div
>
<Editor
textAlignment="left" placeholder="Enter something here"
editorState={editorState} onChange={setEditorState} />
</div>
</div>
)
}

Show div's original position as user moves div across page

I have a sortable list.
When the user drags a list item, I'd like to stylize the item's original position until the user stops dragging.
Along the lines of this:
In this case, there is a border around a blank space in the list, indicating Poland Spring's original position.
My original idea was to place a container div directly behind each li item. However, for that to work, the container div would need to be position:relative and the li items would need to be position:absolute but for the purpose of a list, li items need to be position:relative.
Here is my relevant code:
import styled from '#emotion/styled';
const App = styled('div')`
font-family: sans-serif;
font-size: 1.5rem;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
ul {
margin: 0;
padding: 0;
list-style: none; !important
}
ul li {
background-color: #D3D3D3;
padding: 10px 20px;
position: relative;
//the line below does not work
//position: absolute;
display: flex;
line-height: 1;
list-style-type: none;
border-style: solid;
margin-top:10px;
cursor: pointer
}
`;
const Container = styled('div')`
background: red;
border-color: coral;
position: relative;
`;
class DragDropList extends React.Component {
*/ ....*/
onDragStart = (e, index) => {
this.draggedItem = this.state.items[index];
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', e.target.parentNode);
//e.dataTransfer.setDragImage(e.target.parentNode, 20, 20);
};
onDragOver = index => {
const draggedOverItem = this.state.items[index];
// if the item is dragged over itself, ignore
if (this.draggedItem === draggedOverItem) {
return;
}
// filter out the currently dragged item
let order = this.state.order.filter(item => item !== this.draggedItem);
// add the dragged item after the dragged over item
order.splice(index, 0, this.draggedItem);
this.setState({ order });
};
onDragEnd = index => {
this.draggedIdx = null;
// filter out the currently dragged item
let items = this.state.order;
this.setState({ items });
};
render() {
return (
<App>
<main>
<ul>
{this.state.items.map((item, idx) => (
<div>
<Container>
<li
key={item + `idx`}
onDragOver={() => this.onDragOver(idx)}
draggable
onDragStart={e =>
this.onDragStart(e, idx)
}
onDragEnd={() => this.onDragEnd(idx)}
>
<div
draggable
onDragStart={e =>
this.onDragStart(e, idx)
}
onDragEnd={() =>
this.onDragEnd(idx)
}
>
<span
className="content"
style={{ cursor: 'pointer' }}
>
{item}
</span>
</div>
</li>
</Container>
</div>
))}
</ul>
</main>
</App>
);
}
}
Here is a sandbox: https://codesandbox.io/s/black-dust-hqm2t?fontsize=14
Not sure if this is the best way to do it, but I changed the CSS of the dragged item by storing the dragged index in the state and adding a simple conditional to change the className if it matches the dragged index. To achieve the exact effect you posted may require a bit more CSS trickery or other conditional rendering, but this should cover the basic functionality.
Forked sandbox.

CSS Scroll Snap Points with navigation (next, previous) buttons

I am building a carousel, very minimalist, using CSS snap points. It is important for me to have CSS only options, but I'm fine with enhancing a bit with javascript (no framework).
I am trying to add previous and next buttons to scroll programmatically to the next or previous element. If javascript is disabled, buttons will be hidden and carousel still functionnal.
My issue is about how to trigger the scroll to the next snap point ?
All items have different size, and most solution I found require pixel value (like scrollBy used in the exemple). A scrollBy 40px works for page 2, but not for others since they are too big (size based on viewport).
function goPrecious() {
document.getElementById('container').scrollBy({
top: -40,
behavior: 'smooth'
});
}
function goNext() {
document.getElementById('container').scrollBy({
top: 40,
behavior: 'smooth'
});
}
#container {
scroll-snap-type: y mandatory;
overflow-y: scroll;
border: 2px solid var(--gs0);
border-radius: 8px;
height: 60vh;
}
#container div {
scroll-snap-align: start;
display: flex;
justify-content: center;
align-items: center;
font-size: 4rem;
}
#container div:nth-child(1) {
background: hotpink;
color: white;
height: 50vh;
}
#container div:nth-child(2) {
background: azure;
height: 40vh;
}
#container div:nth-child(3) {
background: blanchedalmond;
height: 60vh;
}
#container div:nth-child(4) {
background: lightcoral;
color: white;
height: 40vh;
}
<div id="container">
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
</div>
<button onClick="goPrecious()">previous</button>
<button onClick="goNext()">next</button>
Nice question! I took this as a challenge.
So, I increased JavaScript for it to work dynamically. Follow my detailed solution (in the end the complete code):
First, add position: relative to the .container, because it need to be reference for scroll and height checkings inside .container.
Then, let's create 3 global auxiliary variables:
1) One to get items scroll positions (top and bottom) as arrays into an array. Example: [[0, 125], [125, 280], [280, 360]] (3 items in this case).
3) One that stores half of .container height (it will be useful later).
2) Another one to store the item index for scroll position
var carouselPositions;
var halfContainer;
var currentItem;
Now, a function called getCarouselPositions that creates the array with items positions (stored in carouselPositions) and calculates the half of .container (stored in halfContainer):
function getCarouselPositions() {
carouselPositions = [];
document.querySelectorAll('#container div').forEach(function(div) {
carouselPositions.push([div.offsetTop, div.offsetTop + div.offsetHeight]); // add to array the positions information
})
halfContainer = document.querySelector('#container').offsetHeight/2;
}
getCarouselPositions(); // call it once
Let's replace the functions on buttons. Now, when you click on them, the same function will be called, but with "next" or "previous" argument:
<button onClick="goCarousel('previous')">previous</button>
<button onClick="goCarousel('next')">next</button>
Here is about the goCarousel function itself:
First, it creates 2 variables that store top scroll position and bottom scroll position of carousel.
Then, there are 2 conditionals to see if the current carousel position is on most top or most bottom.
If it's on top and clicked "next" button, it will go to the second item position. If it's on bottom and clicked "previous" button, it will go the previous one before the last item.
If both conditionals failed, it means the current item is not the first or the last one. So, it checks to see what is the current position, calculating using the half of the container in a loop with the array of positions to see what item is showing. Then, it combines with "previous" or "next" checking to set the correct next position for currentItem variable.
Finally, it goes to the correct position through scrollTo using currentItem new value.
Below, the complete code:
var carouselPositions;
var halfContainer;
var currentItem;
function getCarouselPositions() {
carouselPositions = [];
document.querySelectorAll('#container div').forEach(function(div) {
carouselPositions.push([div.offsetTop, div.offsetTop + div.offsetHeight]); // add to array the positions information
})
halfContainer = document.querySelector('#container').offsetHeight/2;
}
getCarouselPositions(); // call it once
function goCarousel(direction) {
var currentScrollTop = document.querySelector('#container').scrollTop;
var currentScrollBottom = currentScrollTop + document.querySelector('#container').offsetHeight;
if (currentScrollTop === 0 && direction === 'next') {
currentItem = 1;
} else if (currentScrollBottom === document.querySelector('#container').scrollHeight && direction === 'previous') {
console.log('here')
currentItem = carouselPositions.length - 2;
} else {
var currentMiddlePosition = currentScrollTop + halfContainer;
for (var i = 0; i < carouselPositions.length; i++) {
if (currentMiddlePosition > carouselPositions[i][0] && currentMiddlePosition < carouselPositions[i][1]) {
currentItem = i;
if (direction === 'next') {
currentItem++;
} else if (direction === 'previous') {
currentItem--
}
}
}
}
document.getElementById('container').scrollTo({
top: carouselPositions[currentItem][0],
behavior: 'smooth'
});
}
window.addEventListener('resize', getCarouselPositions);
#container {
scroll-snap-type: y mandatory;
overflow-y: scroll;
border: 2px solid var(--gs0);
border-radius: 8px;
height: 60vh;
position: relative;
}
#container div {
scroll-snap-align: start;
display: flex;
justify-content: center;
align-items: center;
font-size: 4rem;
}
#container div:nth-child(1) {
background: hotpink;
color: white;
height: 50vh;
}
#container div:nth-child(2) {
background: azure;
height: 40vh;
}
#container div:nth-child(3) {
background: blanchedalmond;
height: 60vh;
}
#container div:nth-child(4) {
background: lightcoral;
color: white;
height: 40vh;
}
<div id="container">
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
</div>
<button onClick="goCarousel('previous')">previous</button>
<button onClick="goCarousel('next')">next</button>
Another good detail to add is to call getCarouselPositions function again if the window resizes:
window.addEventListener('resize', getCarouselPositions);
That's it.
That was cool to do. I hope it can help somehow.
I've just done something similar recently. The idea is to use IntersectionObserver to keep track of which item is in view currently and then hook up the previous/next buttons to event handler calling Element.scrollIntoView().
Anyway, Safari does not currently support scroll behavior options. So you might want to polyfill it on demand with polyfill.app service.
let activeIndex = 0;
const container = document.querySelector("#container");
const elements = [...document.querySelectorAll("#container div")];
function handleIntersect(entries){
const entry = entries.find(e => e.isIntersecting);
if (entry) {
const index = elements.findIndex(
e => e === entry.target
);
activeIndex = index;
}
}
const observer = new IntersectionObserver(handleIntersect, {
root: container,
rootMargin: "0px",
threshold: 0.75
});
elements.forEach(el => {
observer.observe(el);
});
function goPrevious() {
if(activeIndex > 0) {
elements[activeIndex - 1].scrollIntoView({
behavior: 'smooth'
})
}
}
function goNext() {
if(activeIndex < elements.length - 1) {
elements[activeIndex + 1].scrollIntoView({
behavior: 'smooth'
})
}
}
#container {
scroll-snap-type: y mandatory;
overflow-y: scroll;
border: 2px solid var(--gs0);
border-radius: 8px;
height: 60vh;
}
#container div {
scroll-snap-align: start;
display: flex;
justify-content: center;
align-items: center;
font-size: 4rem;
}
#container div:nth-child(1) {
background: hotpink;
color: white;
height: 50vh;
}
#container div:nth-child(2) {
background: azure;
height: 40vh;
}
#container div:nth-child(3) {
background: blanchedalmond;
height: 60vh;
}
#container div:nth-child(4) {
background: lightcoral;
color: white;
height: 40vh;
}
<div id="container">
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
</div>
<button onClick="goPrevious()">previous</button>
<button onClick="goNext()">next</button>
An easier approach done with react.
export const AppCarousel = props => {
const containerRef = useRef(null);
const carouselRef = useRef(null);
const [state, setState] = useState({
scroller: null,
itemWidth: 0,
isPrevHidden: true,
isNextHidden: false
})
const next = () => {
state.scroller.scrollBy({left: state.itemWidth * 3, top: 0, behavior: 'smooth'});
// Hide if is the last item
setState({...state, isNextHidden: true, isPrevHidden: false});
}
const prev = () => {
state.scroller.scrollBy({left: -state.itemWidth * 3, top: 0, behavior: 'smooth'});
setState({...state, isNextHidden: false, isPrevHidden: true});
// Hide if is the last item
// Show remaining
}
useEffect(() => {
const items = containerRef.current.childNodes;
const scroller = containerRef.current;
const itemWidth = containerRef.current.firstElementChild?.clientWidth;
setState({...state, scroller, itemWidth});
return () => {
}
},[props.items])
return (<div className="app-carousel" ref={carouselRef}>
<div className="carousel-items shop-products products-swiper" ref={containerRef}>
{props.children}
</div>
<div className="app-carousel--navigation">
<button className="btn prev" onClick={e => prev()} hidden={state.isPrevHidden}><</button>
<button className="btn next" onClick={e => next()} hidden={state.isNextHidden}>></button>
</div>
</div>)
}
I was struggling with the too while working with a react project and came up with this solution. Here's a super basic example of the code using react and styled-components.
import React, { useState, useRef } from 'react';
import styled from 'styled-components';
const App = () => {
const ref = useRef();
const [scrollX, setScrollX] = useState(0);
const scrollSideways = (px) => {
ref.current.scrollTo({
top: 0,
left: scrollX + px,
behavior: 'smooth'
});
setScrollX(scrollX + px);
};
return (
<div>
<List ref={ref}>
<ListItem color="red">Card 1</ListItem>
<ListItem color="blue">Card 2</ListItem>
<ListItem color="green">Card 3</ListItem>
<ListItem color="yellow">Card 4</ListItem>
</List>
<button onClick={() => scrollSideways(-600)}> Left </button>
<button onClick={() => scrollSideways(600)}> Right </button>
</div>
);
};
const List = styled.ul`
display: flex;
overflow-x: auto;
padding-inline-start: 40px;
scroll-snap-type: x mandatory;
list-style: none;
padding: 40px;
width: 700px;
`;
const ListItem = styled.li`
display: flex;
flex-shrink: 0;
scroll-snap-align: start;
background: ${(p) => p.color};
width: 600px;
margin-left: 15px;
height: 200px;
`;

Categories