Question about the animation effect of the React carousel component - javascript

I wrote a carousel component with react and react-transition-group, but the animation of the carousel is not working properly. When the image changes from 0 to 1, the 0 will disappear immediately, instead of waiting for the animation to end and then disappear.
The Code Link is as follow
https://stackblitz.com/edit/react-ts-eawtuk?file=CarouselItem.tsx
The core codes are as follow
Carousel Component
import React, {
FC,
Fragment,
ReactNode,
useMemo,
useState,
} from 'react';
import CarouselItem, { ItemProps } from './CarouselItem';
import './Carousel.scss';
export interface Props {}
const Component: FC<Props> = (props) => {
const { children } = props;
const [curIndex, setCurIndex] = useState(1);
const length = useMemo(() => {
return Array.from(children as ReactNode[]).length;
}, [children]);
const onNext = () => {
setCurIndex((curIndex + 1 + length) % length);
};
const onPrev = () => {
setCurIndex((curIndex - 1 + length) % length);
};
setTimeout(onNext, 3000);
return (
<Fragment>
<button onClick={onPrev}>prev</button>
<button onClick={onNext}>next</button>
<div className="g-carousel">
<div className="g-carousel-window">
<div className="g-carousel-wrapper">
{React.Children.map(children, (child, index) => {
const ChildElement = child as FC<ItemProps>;
if (child.type !== CarouselItem) throw new Error('必须是Item');
return React.cloneElement(ChildElement, { index, curIndex });
})}
</div>
</div>
</div>
</Fragment>
);
};
type CarouselType = {
Item: FC<ItemProps>;
} & FC<Props>;
const Carousel: CarouselType = Component as CarouselType;
Carousel.Item = CarouselItem;
export default Carousel;
CarouselItem Component
import React, { CSSProperties, FC, Fragment, useMemo } from 'react';
import { CSSTransition } from 'react-transition-group';
export interface ItemProps {
curIndex?: number;
index?: number;
style?: CSSProperties;
}
const carouselItem: FC<ItemProps> = (props) => {
const { children, index, curIndex } = props;
const visible = useMemo(() => curIndex === index, [curIndex]);
return (
<Fragment>
<CSSTransition
in={visible}
classNames="carousel"
timeout={500}
key={index}
>
<div
className="g-carousel-item"
style={{ display: curIndex !== index && 'none' }}
>
{children}
</div>
</CSSTransition>
</Fragment>
);
};
export default carouselItem;
And css
.g-carousel {
&-window {
overflow: hidden;
}
&-wrapper {
position: relative;
}
}
.carousel-exit {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
.carousel-enter,
.carousel-exit {
transition: all 0.5s;
}
.carousel-enter-active {
transform: translateX(-100%);
}
.carousel-enter.reverse {
transform: translateX(100%);
}
.carousel-exit-done {
transform: translateX(100%);
}
.carousel-exit-done.reverse {
transform: translateX(-100%);
}

Related

React: spinner effect is canceling my scrolling effect

OK guys, so I'm noob in React, and I know that I'm mistaken something, but I can't see what.
I put loading spinners between my pages and no problems, until this page :
So I have this page that is scrolling on X axe and a logo spinning during scroll, until know, all was working correctly. And there is the code without the loading spinner :
import React, { useEffect, useRef } from "react";
import styled, { ThemeProvider } from "styled-components";
import { DarkTheme } from "./Themes";
import {motion} from 'framer-motion';
import LogoComponent from '../subComponents/LogoComponent';
import SocialIcons from '../subComponents/SocialIcons';
import PowerButton from '../subComponents/PowerButton';
import { Work } from '../data/WorkData';
import Card from "../subComponents/Card";
import { Umbrella } from "./AllSvgs";
import BigTitle from "../subComponents/BigTitle";
const Box = styled.div`
background-color: ${props => props.theme.body};
height: 400vh;
position: relative;
display: flex;
align-items: center;
`
const Main = styled(motion.ul)`
position: fixed;
top: 12rem;
left: calc(10rem + 15vw);
height: 40vh;
display: flex;
color: white;
`
const Rotate = styled.span`
display: block;
position: fixed;
right: 1rem;
bottom: 1rem;
width: 80px;
height: 80px;
z-index:1;
`
// Framer-motion configuration
const container = {
hidden: {opacity:0},
show: {
opacity:1,
transition: {
staggerChildren:0.5,
duration:0.5,
}
}
}
const WorkPage = () => {
const ref = useRef(null);
const umbrella = useRef(null);
useEffect(() => {
let element = ref.current;
const rotate = () => {
element.style.transform = `translateX(${-window.scrollY}px)`
umbrella.current.style.transform = `rotate(` + -window.scrollY + 'deg)'
}
window.addEventListener('scroll', rotate)
return () => window.removeEventListener('scroll', rotate)
}, [])
return (
<ThemeProvider theme={DarkTheme}>
<Box>
<LogoComponent theme='dark'/>
<SocialIcons theme='dark'/>
<PowerButton />
<Main ref={ref} variants={container} initial='hidden' animate='show' >
{
Work.map( d =>
<Card key={d.id} data={d} />
)
}
</Main>
<Rotate ref={umbrella}>
<Umbrella width={80} height={80} fill={DarkTheme.theme} />
</Rotate>
<BigTitle text="PROJETS" top="10%" right="20%" />
</Box>
</ThemeProvider>
)
}
export default WorkPage
Then I put the code with the same logic than the other pages that are working :
import React, { useEffect, useRef, useState } from "react";
import styled, { ThemeProvider } from "styled-components";
import { DarkTheme } from "./Themes";
import {motion} from 'framer-motion';
import RingLoader from "react-spinners/RingLoader";
import { css } from "#emotion/react";
import LogoComponent from '../subComponents/LogoComponent';
import SocialIcons from '../subComponents/SocialIcons';
import PowerButton from '../subComponents/PowerButton';
import { Work } from '../data/WorkData';
import Card from "../subComponents/Card";
import { Umbrella } from "./AllSvgs";
import BigTitle from "../subComponents/BigTitle";
const Box = styled.div`
background-color: ${props => props.theme.body};
height: 400vh;
position: relative;
display: flex;
align-items: center;
`
const Main = styled(motion.ul)`
position: fixed;
top: 12rem;
left: calc(10rem + 15vw);
height: 40vh;
display: flex;
color: white;
`
const Rotate = styled.span`
display: block;
position: fixed;
right: 1rem;
bottom: 1rem;
width: 80px;
height: 80px;
z-index:1;
`
const override = css`
position: absolute;
bottom: 10%;
right: 10%;
`
// Framer-motion configuration
const container = {
hidden: {opacity:0},
show: {
opacity:1,
transition: {
staggerChildren:0.5,
duration:0.5,
}
}
}
const WorkPage = () => {
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true)
setTimeout(() => {
setLoading(false)
}, 2000)
}, [])
const ref = useRef(null);
const umbrella = useRef(null);
useEffect(() => {
let element = ref.current;
const rotate = () => {
element.style.transform = `translateX(${-window.scrollY}px)`
umbrella.current.style.transform = `rotate(` + -window.scrollY + 'deg)'
}
window.addEventListener('scroll', rotate)
return () => window.removeEventListener('scroll', rotate)
}, [])
return (
<ThemeProvider theme={DarkTheme}>
{
loading ?
<RingLoader
color={'#000'}
loading={loading}
size={60}
css={override}
/>
:
<Box>
<LogoComponent theme='dark'/>
<SocialIcons theme='dark'/>
<PowerButton />
<Main ref={ref} variants={container} initial='hidden' animate='show' >
{
Work.map( d =>
<Card key={d.id} data={d} />
)
}
</Main>
<Rotate ref={umbrella}>
<Umbrella width={80} height={80} fill={DarkTheme.theme} />
</Rotate>
<BigTitle text="PROJETS" top="10%" right="20%" />
</Box>
}
</ThemeProvider>
)
}
export default WorkPage
And the scroll is not working anymore. The logo is still spinning. I try to put in conditions, but nope.
Put the useState on true, but then I have an error on the rotate func :
TypeError: Cannot read properties of null (reading 'style')
rotate
src/components/WorkPage.js:89
86 | let element = ref.current;
87 |
88 | const rotate = () => {
> 89 | element.style.transform = `translateX(${-window.scrollY}px)`
| ^ 90 |
91 | umbrella.current.style.transform = `rotate(` + -window.scrollY + 'deg)'
92 | }
I don't see what I'm missing... Thanks y'all 🙏
In your second example code (where you added loading and setLoading from the useState hook), the <Box> component (which contains the <Main ref={ref}... component) doesn't get rendered when loading is set to true (and you're setting it to true in your first useEffect).
Since the <Main ref={ref}... component isn't being rendered, the line let element = ref.current will initialize element as null. (that's why you're getting that error)

How to switch image on scroll in React (like on Apple website)?

I try to switch from an image to another one when scrolling with React but I don't know how to do this... maybe with React hook. I know it's possible with jQuery but I don't want to use it.
Example here: https://www.apple.com/iphone-12/, look at the "Five fresh finishes".
import React, {useState} from 'react';
import './BackgroundDiscover.css';
import logoOrdiW from "../../images/mathis_computer_white.jpg";
import logoOrdiB from "../../images/mathis_computer_black.jpg";
const BackgroundDiscover = () => {
const [imageSrc, SetSrc] = useState(logoOrdiW);
const switchBrackground = () => {
SetSrc(logoOrdiB);
}
return (
<>
<div className="container-discover">
<div className='container-logo-white'>
<img src={imageSrc} alt="logo ordinateur mathis blanc" onScroll={switchBrackground}/>
</div>
</div>
</>
);
};
export default BackgroundDiscover;
.container-discover {
display: flex;
flex-direction: column;
}
.container-logo-white img {
width: 100%;
}
.container-logo-black img {
width: 100%;
}
You don't need jQuery or something like this. You could subscribe to scroll event in useEffect() hook with addEventListener and check scrollTop of a scrolling container.
If you want to do the same effect as the Apple website has, you could add some magic with position: sticky, height in vh units and checking window.innerHeight.
Note that this code is simplified, not optimized, and only needed to understand the idea:
CodeSandbox
index.js:
import { useLayoutEffect, useState } from "react";
import { render } from "react-dom";
import classnames from "classnames";
import "./index.css";
const images = [0, 1, 2, 3, 4];
const App = () => {
const [visibleImagesMap, setVisibleImagesMap] = useState(
images.reduce((map, image) => {
map[image] = false;
return map;
}, {})
);
useLayoutEffect(() => {
const handleScroll = () => {
const scrollTop = document.documentElement.scrollTop;
const viewportHeight = window.innerHeight;
const newVisibleImagesMap = images.reduce((map, image) => {
map[image] = scrollTop >= image * viewportHeight;
return map;
}, {});
setVisibleImagesMap(newVisibleImagesMap);
};
window.addEventListener("scroll", handleScroll);
handleScroll();
return () => window.removeEventListener("scroll", handleScroll);
}, []);
return (
<div className="app">
<div className="sticky">
<div className="frame">
{images.map((image) => (
<div
className={classnames("image", `image_${image}`, {
image_visible: visibleImagesMap[image]
})}
key={image}
/>
))}
</div>
</div>
</div>
);
};
render(<App />, document.getElementById("root"));
index.css:
body {
margin: 0;
}
.app {
height: 500vh;
}
.sticky {
position: sticky;
top: 0;
height: 100vh;
}
.frame {
z-index: 1;
position: relative;
height: 100%;
width: 100%;
}
.image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
background-repeat: no-repeat;
background-size: cover;
}
.image_0 {
z-index: 0;
background-image: url("./images/0.jpeg");
}
.image_1 {
z-index: 1;
background-image: url("./images/1.jpeg");
}
.image_2 {
z-index: 2;
background-image: url("./images/2.jpeg");
}
.image_3 {
z-index: 3;
background-image: url("./images/3.jpeg");
}
.image_4 {
z-index: 4;
background-image: url("./images/4.jpeg");
}
.image_visible {
opacity: 1;
}
There are plenty of solutions for this but basically you can set in this way.
Wheel event listener turns either positive or negative value so you should create an algorithm if you want to impelement different designs.
import React, { useState } from "react";
export default function App() {
const [bgImage, handleImage] = useState(
"https://media.wired.com/photos/5e9f56f143e5800008514457/1:1/w_1277,h_1277,c_limit/Gear-Feature-Apple_new-iphone-se-white_04152020.jpg"
);
window.addEventListener("wheel", (e) => {
if (e.deltaY > 0) {
handleImage(
"https://media.wired.com/photos/5bcea2642eea7906bba84c67/master/w_2560%2Cc_limit/iphonexr.jpg"
);
} else {
handleImage(
"https://media.wired.com/photos/5e9f56f143e5800008514457/1:1/w_1277,h_1277,c_limit/Gear-Feature-Apple_new-iphone-se-white_04152020.jpg"
);
}
});
return (
<div className="App">
<img
style={{ width: "400px", height: "300px" }}
id="myimage"
src={bgImage}
alt=""
/>
</div>
);
}
And as a result you can use bgImage state in your image src.
This is a codesandbox example https://codesandbox.io/s/pensive-vaughan-cfc63?fontsize=14&hidenavigation=1&theme=dark
You can try useEffect and useRef.
useEffect is sideload functions, and useRef is for real dom referances.
import React, { useRef, useState, useEffect } from 'react';
import logoOrdiW from "../../images/mathis_computer_white.jpg";
import logoOrdiB from "../../images/mathis_computer_black.jpg";
import './BackgroundDiscover.css';
const BackgroundDiscover = () => {
const img = useRef();
const [src, setSrc] = useState(logoOrdiW);
const switchBrackground = () => {
setSrc(logoOrdiB);
}
useEffect(() => {
img.current.onscroll = switchBrackground;
}, []);
return (
<div className="container-discover">
<div className='container-logo-white'>
<img src={src} alt="logo ordinateur mathis blanc" ref={img} />
</div>
</div>
);
};
export default BackgroundDiscover;

Can't resize React-Player Video

Can't resize React-Player Video.
I change the width to any number and the video stays unchanged.
I am trying to optimize it for computer view and then add breakpoints to resize for smaller screens like phones.
Bellow is the file where I render the React-Player video inside a on which I apply the desired height and width I would like my react-player video to adopt.
import React, { useContext, useEffect, useState } from 'react'
import { makeStyles } from '#material-ui/core/styles'
import Modal from '#material-ui/core/Modal'
import { MovieContext } from './MovieContext'
import ReactPlayer from 'react-player'
import { getTrailer } from '../utils/movieDB'
// potential of adding controls
// import { Slider, Direction } from 'react-player-controls'
//slider to be implemented
//https://www.npmjs.com/package/react-player-controls#playericon-
const useStyles = makeStyles((theme) => ({
video: {
width: 'auto',
height: 'auto',
top: '25%',
right: '25%',
position: 'fixed',
[theme.breakpoints.down('xs')]: {},
},
}))
const styles = {
player: {
width: '300px',
},
}
export default function SimpleModal({ open }) {
const classes = useStyles(),
//receives movie from Home > DisplayCard > MovieContext
{ setOpenTrailer, movie, setMovie } = useContext(MovieContext),
[trailer, setTrailer] = useState(),
[key, setKey] = useState(),
[modalStyle] = useState()
useEffect(() => {
if (movie) {
getTrailer(movie).then((data) => {
setKey(data.videos.results[0].key)
setTrailer(data)
})
}
}, [movie])
const handleOpen = () => {
setOpenTrailer(true)
}
const handleClose = () => {
setOpenTrailer(false)
setMovie(undefined)
setTrailer(undefined)
setKey(undefined)
}
const renderVideo = (
<>
{key && (
<div className={classes.video}>
<ReactPlayer style={styles.player} url={`https://www.youtube.com/watch?v=${key}`} />
</div>
)}
</>
)
return (
<div>
<Modal
open={open || false}
onClose={handleClose}
aria-labelledby="simple-modal-title"
aria-describedby="simple-modal-description"
>
{renderVideo}
</Modal>
</div>
)
}
Which is the proper way to set the dimensions on a react-player object?
Using the width and height props as such:
<ReactPlayer
width={“300px”}
url={`https://www.youtube.com/watch?v=${key}`} />
this code is from the react-player library. This made my video responsive
its from: https://github.com/cookpete/react-player
class ResponsivePlayer extends Component {
render () {
return (
<div className='player-wrapper'>
<ReactPlayer
className='react-player'
url='https://www.youtube.com/watch?v=ysz5S6PUM-U'
width='100%'
height='100%'
/>
</div>
)
}
}
----- CSS ------
.player-wrapper {
position: relative;
padding-top: 56.25% /* Player ratio: 100 / (1280 / 720) */
}
.react-player {
position: absolute;
top: 0;
left: 0;
}

react-pose animation flashing on mount

I have the following code, which should render a simple fade in animation for the Container component after 3 seconds. However, the component is flashing fully visible before fading in. My question is: why is this happening, and how can I stop it from happening?
import React, { useState, useEffect } from "react";
import { render } from "react-dom";
import posed, { PoseGroup } from "react-pose";
import styled from "styled-components";
const sequence = b =>
b.every(
(a, i) => !(a.call ? a() : setTimeout(() => sequence(b.slice(++i)), a))
);
const usePose = (initial, poses = {}) => {
const [pose, setPose] = useState(initial);
return { pose, setPose, poses };
};
const useAnimation = () => {
const { pose, setPose } = usePose(`hidden`, [`hidden`, `normal`]);
useEffect(() => {
sequence([3000, () => setPose(`normal`)]);
}, []);
return {
pose
};
};
const Container = styled(
posed.div({
hidden: {
opacity: 0
},
normal: { opacity: 1 }
})
)({
color: "red"
});
const App = () => {
const { pose } = useAnimation();
return (
<PoseGroup animateOnMount>
<Container key={0} pose={pose}>
<h1>hello world</h1>
</Container>
</PoseGroup>
);
};
const rootElement = document.getElementById("root");
render(<App />, rootElement);
Issue solved by:
const Container = styled(
posed.div({
hidden: {
opacity: 0
},
normal: { opacity: 1 }
})
)({
color: "red"
opacity: 0, // Add this to stop flash.
});

React-dnd not working in Cordova on iOS

I'm using the react-dnd-touch-backend.
I'm able to get my DragSources to drag correctly, but the DropTargets don't accept them (or react to being dragged over).
The application only uses one wrapper component for each role (DragSource and DropTarget). I have also defined a custom drag layer. The drag/drop worked fine before adding the custom drag layer except my DragSources were invisible on iOS (which is why I added the drag layer in the first place), but now I can see the DragSources, but the DropTargets don't work.
Any help is much appreciated.
DragSource:
import React from "react";
import cn from "util/cn";
import {isCordova} from "util/detect-platform";
import {DragSource} from "react-dnd";
import {getEmptyImage} from "react-dnd-html5-backend";
require("./style.scss");
const TYPE = "DRAG-CONTAINER";
const source = {
beginDrag({value, left, top, children, DragPreviewComponent}) {
return {value, left, top, children, DragPreviewComponent};
}
};
function collect(connect, monitor) {
return {
connectDragSource: connect.dragSource(),
connectDragPreview: connect.dragPreview(),
isDragging: monitor.isDragging()
};
}
function getStyles(props) {
const {left, top, isDragging} = props;
const transform = `translate3d(${left}px, ${top}px, 0)`;
return {
transform: transform,
WebkitTransform: transform,
opacity: isDragging ? 0 : 1
};
}
#DragSource(TYPE, source, collect)
export default class DragContainer extends React.Component {
static propTypes = {
value: React.PropTypes.any
};
static defaultProps = {style: {}};
componentDidMount() {
if(!isCordova()) {
this.props.connectDragPreview(getEmptyImage(), {
captureDraggingState: true
});
}
}
render() {
const {className, isDragging, connectDragSource, style} = this.props;
const classNames = cn(
"Drag-container",
isDragging ? "Drag-container--dragging" : null,
className
);
return connectDragSource(
<div {...this.props} className={classNames} value={null} style={{...style, ...getStyles(this.props)}}/>
);
}
}
DropTarget:
import React from "react";
import {DropTarget} from "react-dnd";
import cn from "util/cn";
require("./style.scss");
const TYPE = "DRAG-CONTAINER";
const target = {
drop(props, monitor) {
const {onDrop} = props;
const {value} = (monitor.getItem() || {value: null});
if(typeof onDrop === "function") {
setTimeout(() => onDrop(value), 100);
}
}
};
function collect(connect, monitor) {
return {
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver()
};
}
#DropTarget(TYPE, target, collect)
export default class DropContainer extends React.Component {
static propTypes = {
onDrop: React.PropTypes.func
};
render() {
const {connectDropTarget, isOver, className} = this.props;
const classNames = cn("Drop-container", isOver ? "Drop-container--over" : null, className);
return connectDropTarget(
<div {...this.props} className={classNames} onDrop={null} onDragEnter={null} onDragExit={null}/>
);
}
}
Custom Drag Layer:
import React from "react";
import {DragLayer} from "react-dnd";
const layerStyles = {
position: "fixed",
pointerEvents: "none",
width: "100%",
height: "100%",
zIndex: 100,
left: 0,
top: 0
};
function getItemStyles(props) {
const { initialOffset, currentOffset } = props;
if (!initialOffset || !currentOffset) {
return {
display: 'none'
};
}
let { x, y } = currentOffset;
if (props.snapToGrid) {
x -= initialOffset.x;
y -= initialOffset.y;
[x, y] = snapToGrid(x, y);
x += initialOffset.x;
y += initialOffset.y;
}
const transform = `translate(${x}px, ${y}px)`;
return {
transform: transform,
WebkitTransform: transform
};
}
#DragLayer(monitor => ({
item: monitor.getItem(),
itemType: monitor.getItemType(),
initialOffset: monitor.getInitialSourceClientOffset(),
currentOffset: monitor.getSourceClientOffset(),
isDragging: monitor.isDragging()
}))
export default class CustomDragLayer extends React.Component {
render() {
const {item, itemType, isDragging} = this.props;
if (!isDragging || !item) return null;
const {DragPreviewComponent} = item;
if(!DragPreviewComponent) return null;
return (
<div style={layerStyles}>
<div style={getItemStyles(this.props)}>
<DragPreviewComponent {...item}/>
</div>
</div>
);
}
}
The problem was this issue in the react-dnd-touch-backend library:
https://github.com/yahoo/react-dnd-touch-backend/issues/34
Rolling back to version 0.2.7 fixed the issue.

Categories

Resources