Is there anyway to have a React Native navigation transition by fading to black, then fade out from black to the next screen?
Have been googling for awhile, I have only found methods of changing the screens opacity, or moving it from left to right or right to left, But I haven't found a transition from a screen to black. Any help would be much appreciated.
If you are using latest version of react-navigation-stack, you can use CardAnimationContext/useCardAnimation to achieve this:
import * as React from 'react';
import { Animated, Button, View, StyleSheet } from 'react-native';
import { createAppContainer } from 'react-navigation';
import {
createStackNavigator,
CardStyleInterpolators,
HeaderStyleInterpolators,
useCardAnimation,
} from 'react-navigation-stack';
function ScreenA({ navigation }) {
return (
<View style={{ flex: 1 }}>
<Button onPress={() => navigation.push('ScreenB')} title="Go to B" />
</View>
);
}
function ScreenB() {
const { current } = useCardAnimation();
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Animated.View
style={[
StyleSheet.absoluteFill,
{ backgroundColor: 'black', opacity: current.progress },
]}
/>
</View>
);
}
const Stack = createStackNavigator({
ScreenA,
ScreenB,
});
Just place a black <div> behind that screen and
Thanks #Prajwal, CSS transition solution:
document.getElementsByTagName(`main`)[0].style.opacity = `0`;
main {
position: fixed; /* place it before .black */
background-color: white;
width: 100%;
height: 100%;
transition-property: opacity; /* set opacity to one of the transition properties */
transition-duration: 4s; /* set transition duration */
}
.black {
position: fixed; /* place it behind main */
background-color: black; /* make it black */
width: 100%; /* make it fill available space */
height: 100%; /* make it fill available space*/
}
body {
margin: 0; /* make it fill available space*/
}
<body>
<div class="black"></div>
<main>
<p>lorem</p>
</main>
</body>
You can also use the setInterval() function to lower the opacity bit by bit.
const step = 0.05; // set the step of opacity change
const timeInterval = 200; // set the sleep time of opacity change
const times = 1 / step; // calculate times needed for opacity change
document.getElementsByTagName(`main`)[0].style.opacity = `1`; // set initial opacity to make getting the float value of it work.
let time = 0; // initially, the time of making changes is zero
const lowerOpacity = window.setInterval(function() {
if (time + 1 === times) {
window.clearInterval(lowerOpacity);
} // clearInterval() when the time of changes has reached the needed times
document.getElementsByTagName(`main`)[0].style.opacity =
`${parseFloat(document.getElementsByTagName(`main`)[0].style.opacity) -
0.05}`; // set the opacity to make it dimmer
time += 1; // increment time to match the changed times
}
, timeInterval);
main {
position: fixed; /* place it before .black */
background-color: white;
width: 100%;
height: 100%;
}
.black {
position: fixed; /* place it behind main */
background-color: black; /* make it black */
width: 100%; /* make it fill available space */
height: 100%; /* make it fill available space*/
}
body {
margin: 0; /* make it fill available space*/
}
<body>
<div class="black"></div>
<main>
<p>lorem</p>
</main>
</body>
Related
When my sidebar transitions to width: 0, the content right next to it (on its right) doesn't slide with it. It's like the text waits for the sidebar to be done with its animation before it takes the sidebar's place, even though I set its transition as well.
I came up with a minimal reproducible example below:
//Sidebar.js
import './styles/Sidebar.css'
export const Sidebar = () => {
const [show, setShow] = useState(false);
const toggle = ()=>{
setShow(!show);
}
return (
<div>
<div id={'toggle-btn'}>
<button type='button' className='toggle-btn' onClick={toggle}>
toggle
</button>
</div>
<div style={{display:"flex"}}>
<aside className={'sidebar' + (show ? ' showSidebar':'')}>
<ul className='menuList'>
<li>Perfil</li>
<li>Estatísticas</li>
<li>Adicionar Itens</li>
<li>Procurar</li>
</ul>
</aside>
</div>
</div>
)
}
/*Sidebar.css*/
.sidebar {
width:100%;
overflow: hidden;
box-shadow: 0 8px 8px -4px ;
transform: translateX(0);
transition:all 1s ease;
height:100vh;
}
.showSidebar {
width:0;
}
//Dashboard.js
import './styles/Dashboard.css'
export const Dashboard = () => {
return (
<div className='dashboard'>
<p>
LORE IPSUM BLA BLA
</p>
</div>
)
}
/*Dashboard.css*/
.dashboard {
max-width: 30%;
margin-top:10rem;
transition:all 1s ease;
}
//App.js
function App() {
return (
<div style={{display:"flex"}}>
<Sidebar />
<Dashboard />
</div>
);
}
export default App;
When you change the Sidebar's width from 100% to 0, it simply is taken out of the content flow, and Dashboard then is reposition to left. To make Sidebar and Dashboard transition together while one of the two has width change, you need to establish a relationship between the two component's widths.
Please refer to this CodeSandbox example I put together for you.
In it, I set up a global CSS variable like below:
/* styles.css */
:root {
--sidebar: 150px;
}
And use it in both Sidebar and Dashboard like below:
/* Sidebar.css */
.sidebar {
width: var(--sidebar);
/* no other changes */
}
/* Dashboard.css */
.dashboard {
width: calc(100% - var(--sidebar));
/* no other changes */
}
With the above, whenever Sidebar's width changes, it'll reflect the value in Dashboard's width, making both transition in sync.
I'm trying to make it so that a box would expand (in width and height) and transition from its origin to the center of a screen upon being clicked. Here's what I have so far:
I'm running into two problems here -- when I click on the box, the DOM automatically shifts, because the clicked element has its position changed to 'absolute'. The other problem is that the box doesn't transition from its origin, it transitions from the bottom right corner (it also doesn't return to its position from the center of the screen, when make inactive is clicked).
What am I doing wrong here?
import React from "react";
import styled from "styled-components";
import "./styles.css";
export default function App() {
const [clickedBox, setClickedBox] = React.useState(undefined);
const handleClick = React.useCallback((index) => () => {
console.log(index);
setClickedBox(index);
});
return (
<Container>
{Array.from({ length: 5 }, (_, index) => (
<Box
key={index}
active={clickedBox === index}
onClick={handleClick(index)}
>
box {index}
{clickedBox === index && (
<div>
<button
onClick={(e) => {
e.stopPropagation();
handleClick(undefined)();
}}
>
make inactive
</button>
</div>
)}
</Box>
))}
</Container>
);
}
const Container = styled.div`
display: flex;
flex-wrap: wrap;
align-items: center;
height: 100vh;
`;
const Box = styled.div`
flex: 1 0 32%;
padding: 0.5rem;
cursor: pointer;
margin: 1rem;
border: 1px solid red;
transition: 2s;
background-color: white;
${({ active }) => `
${
active
? `
position: absolute;
width: 50vw;
height: 50vh;
background-color: tomato;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
`
: ""
}
`}
`;
With CSS
Wery unlikely you can achieve that with plain css. And for sure impossible to achieve a versatile solution.
You have a dynamic size and position to adopt to (starting div)
You have to adapt to the current scrolling position
If you remove the div from the layout is almost impossible to avoid screwing up the layout (even if you can, there will always be some edge case).
transition from a relative to a fixed position.
With the current css standard is impossible to perform these things.
With JS
The solution is to do some javascript magic. Since you are using React i developed you a solution using react-spring (an animation framework). Here you have a wrapping component that will do what you want:
The complete SandBox
import React, { useEffect, useRef, useState } from "react";
import { useSpring, animated } from "react-spring";
export default function Popping(props) {
const cont = useRef(null);
const [oriSize, setOriSize] = useState(null);
const [finalSize, setFinalSize] = useState(null);
useEffect(() => {
if (props.open && cont.current) {
const b = cont.current.getBoundingClientRect();
setOriSize({
diz: 0,
opacity: 0,
top: b.top,
left: b.left,
width: b.width,
height: b.height
});
const w = window.innerWidth,
h = window.innerHeight;
setFinalSize({
diz: 1,
opacity: 1,
top: h * 0.25,
left: w * 0.25,
width: w * 0.5,
height: h * 0.5
});
}
}, [props.open]);
const styles = useSpring({
from: props.open ? oriSize : finalSize,
to: props.open ? finalSize : oriSize,
config: { duration: 300 }
});
return (
<>
<animated.div
style={{
background: "orange",
position: "fixed",
display:
styles.diz?.interpolate((d) => (d === 0 ? "none" : "initial")) ||
"none",
...styles
}}
>
{props.popup}
</animated.div>
<div ref={cont} style={{ border: "2px solid green" }}>
{props.children}
</div>
</>
);
}
Note: This code uses two <div>, one to wrap your content, and the second one is always fixed but hidden. When you toggle the popup visibility, the wrapping div gets measured (we obtain its size and position on the screen) and the fixed div is animated from that position to its final position. You can achieve the illusion you are looking for by rendering the same content in both <div>, but there is always the risk of minor misalignment.
The idea is similar to what newbie did in their post but without any extra libraries. I might have done some things a bit non-standard to avoid using any libraries.
CodeSandbox
import React from "react";
import { StyledBox } from "./App.styles";
export const Box = (props) => {
const boxRef = React.useRef(null);
const { index, active, handleClick } = props;
const handleBoxClick = () => {
handleClick(index);
};
React.useEffect(() => {
const b = boxRef.current;
const a = b.querySelector(".active-class");
a.style.left = b.offsetLeft + "px";
a.style.top = b.offsetTop + "px";
a.style.width = b.offsetWidth + "px";
a.style.height = b.offsetHeight + "px";
});
return (
<StyledBox active={active} onClick={handleBoxClick} ref={boxRef}>
box {index}
<div className="active-class">
box {index}
<div>
<button
onClick={(e) => {
e.stopPropagation();
handleClick(undefined);
}}
>
make inactive
</button>
</div>
</div>
</StyledBox>
);
};
import styled from "styled-components";
export const StyledContainer = styled.div`
display: flex;
flex-wrap: wrap;
align-items: center;
height: 100vh;
`;
export const StyledBox = styled.div`
flex: 1 0 32%;
padding: 0.5rem;
cursor: pointer;
margin: 1rem;
border: 1px solid red;
background-color: white;
.active-class {
position: absolute;
transition: 0.3s all ease-in;
background-color: tomato;
z-index: -1;
${({ active }) =>
active
? `
width: 50vw !important;
height: 50vh !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%);
z-index: 1;
opacity: 1;
`
: `
z-index: -1;
transform: translate(0, 0);
opacity: 0;
`}
}
`;
first, transition with position or display don't work on css(it can work but without transition).
here you have:
flex: 1 0 32%;
that is equivalent to :
flex-grow: 1;
flex-shrink: 1;
flex-basis: 32%;
so, when active is true, width would jump to 50vw and height to 50vh but roughly without transition. so the solution is to use scale like this:
z-index: 99;
transform: scaleY(5) scaleX(2) translate(20%, 20%);
background-tomato: tomato
and you need to tweak the values of scaleY, scaleX and translate (for each Box) until you get it to work.
you can take a look at what i did in this codesandbox: https://codesandbox.io/s/peaceful-payne-ewmxi?file=/src/App.js:1362-1432
here is also a link if you want master flex: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Controlling_Ratios_of_Flex_Items_Along_the_Main_Ax
be sure that all your items have the following css properties : transform: translateX(0) translateY(0); transition: transform Xms timing-function-of-your-choice, left Xms timing, top Xms timing;
Until your page is completly loaded, let all the item in your page have the css property : position: static.
When page loads, retrive the items' properties : x offset from left of screen, y offset from top of document, width and height.
Use javascript to change the items properties : set position to fixed and affect the left, top, width and height css properties from the values we just retrieved. this way, the items will keep their exact position after their position property changes.
with javascript, when the box is clicked on, to center it inside your page, just apply the following css properties via javascript : left: 50%; top: 50%; transform: translateX(-50%) translateY(-50%); width: the width of your choice; height: the height of your choice;
This way, your item will move in the center of your screen no matter what their width and origin offset were. Also, the transition will be very smooth.
You might also want to change the z-index of an item when it is clicked.
I'm trying to position a div element over a sine wave that is used as its path. As the user scrolls, the callback should be able to calculate the left and top properties of the Ball along the sine wave. I'm having trouble with the positioning. I'm using Math.abs for the y-axis and I'm adding 8 (or -8) pixels to handle the x-axis.
Another thing I've noticed is that the scroll event listener callback sometimes missed certain breakpoints. I've console logged the scroll position and its true, the callback is either executed every ~3 pixels or the browser throttles the scroll event on its own for some reason (which I can understand, there's no point in tracking every pixel scroll).
Anyway, I'm wondering why my current approach isn't working and if there's a better solution to this problem? I feel like there's too much stuff going on and that this could be achieved in a better way. Here's what I have:
import React from "react";
import styled from "styled-components";
const lineHeight = 200;
export default React.memo(() => {
const [top, setTop] = React.useState(275);
const [left, setLeft] = React.useState(0);
const [previousPosition, setPreviousPosition] = React.useState(0);
React.useEffect(() => { const s = skrollr.init() }, []);
React.useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, [previousPosition, left]);
const handleScroll = React.useCallback(() => {
const pageYOffset = window.pageYOffset;
const isMovingForward = pageYOffset > previousPosition;
setPreviousPosition(window.pageYOffset);
setTop(lineHeight - Math.abs(lineHeight - pageYOffset));
if (isMovingForward) {
if (pageYOffset > 575 && pageYOffset <= 770) setLeft(left + 8);
} else {
if (pageYOffset <= 770 && pageYOffset >= 575) setLeft(left - 8)
}
}, [previousPosition, left]);
return (
<Main>
<Container
data-500p="transform: translateX(0%)"
data-1000p="transform: translateX(-800%)"
>
<Content>
<WaveContainer>
<Wave src="https://www.designcrispy.com/wp-content/uploads/2017/11/Sine-Wave-Curve.png" />
</WaveContainer>
<BallContainer top={top} left={left}>
<Ball />
</BallContainer>
</Content>
</Container>
</Main>
);
});
const Main = styled.div`
margin: 0;
padding: 0;
box-sizing: border-box;
`;
const Container = styled.div`
position: fixed;
width: 100%;
display: flex;
top: 0;
left: 0;
`;
const Content = styled.div`
min-width: 100vw;
height: auto;
display: grid;
place-items: center;
height: 100vh;
background: #fffee1;
font-weight: 900;
position: relative;
`;
const Wave = styled.img`
width: 600px;
`;
const WaveContainer = styled.div`
position: absolute;
left: -40px;
top: 45%;
`;
const Ball = styled.div`
width: 20px;
height: 20px;
border-radius: 50%;
background: black;
`;
const BallContainer = styled.div`
position: absolute;
transition: 0.25s;
${({ top, left }) => `top: ${top}px; left: ${left}px;`};
`;
I'm using Skrollr to handle the fixed canvas + scroll length.
Codesandbox
I changed your codesandbox so it does what you need: https://codesandbox.io/s/determined-nobel-64odf (I hope)
I should add that I changed various things about your code:
No need to set up new scroll listener every time a left or previous
position changes. What is all the code about previous position and
deciding if page is being scrolled up or down? I removed it.
The animation was being throttled due to transition: 0.25s on your
Ball container. In order to calculate the ball position relatively to
image - sinusoide, I moved them into the same container.
To calculate exact position of ball, WAVE_WIDTH and WAVE_HEIGHT
constants need to be used, and proper mathematics need to be used -
the sinusoide on image seems to be long of 2,5 periods. However 2,58
was better constant to fit the animation. I'd try using different
sinusoide and figure that out.
If you can support offset-path (not available on IE or Safari, apparently), I would strongly suggest moving as much of this as you can to CSS and SVG based animation. Here is a vanilla JS example - the only thing you actually have to calculate here is what percent along you want the animation to be, which could be based on any arbitrary scroll criteria:
const ball = document.querySelector('.ball');
window.addEventListener('scroll', () => {
const winHeight = document.body.offsetHeight;
const pageOffset = window.pageYOffset;
const pc = (pageOffset*2/winHeight)*100;
ball.style.offsetDistance = `${pc}%`;
});
* {
padding: 0;
margin: 0;
}
.page {
height: 200vh;
}
.container {
position: sticky;
top: 0;
width: 100%;
}
svg {
position: absolute;
fill: transparent;
stroke: blue;
stroke-width: 2px;
}
.ball {
offset-path: path("M10 80 C 40 10, 65 10, 95 80 S 150 150, 180 80");
width: 20px;
height: 20px;
background: black;
border-radius: 50%;
}
<div class="page">
<div class="container">
<svg>
<path d="M10 80 C 40 10, 65 10, 95 80 S 150 150, 180 80"/>
</svg>
<div class="ball"></div>
</div>
</div>
The drawbacks to this are that you'll have to recreate your specific path using SVG, which could take a bit of learning time if you aren't familiar with the syntax. The path is also not going to be responsive to screen width so you'd have to recalculate it a few times if that is important to you.
If that's not an option then you're basically going to have to write or find a script which is capable of generating a given sine wave, and then calculating coordinate position along it based on percentage. If it's not exact then it's never going to line up properly.
I'm trying to animate a sidebar component following the first section on this page. When I follow this the component doesn't animate, but simply mounts/unmounts.
The component SidePage is as follows:
import React from "react"
import { TransitionGroup, CSSTransition } from "react-transition-group"
import "./sidePage.css"
class SidePage extends React.Component {
componentWillMount() {
console.log("will mount")
}
componentDidMount() {
console.log("did mount")
}
componentWillUnmount() {
console.log("will unmount")
}
render() {
const { content, sidePageOpen } = this.props
return (
<TransitionGroup component={null}>
{sidePageOpen && (
<CSSTransition key={content.id} classNames="sidepage" timeout={2000}>
<div
key={content.id}
className="sidepage"
dangerouslySetInnerHTML={{ __html: content.html }}
/>
</CSSTransition>
)}
</TransitionGroup>
)
}
}
export default SidePage
and the css file:
.sidepage-enter {
opacity: 0;
}
.sidepage-enter-active {
opacity: 1;
transition: all 2s;
}
.sidepage-exit {
opacity: 1;
}
.sidepage-exit-active {
opacity: 0;
transition: all 2s;
}
.sidepage {
background: white;
padding: 10px;
height: 100%;
width: 90vw;
position: absolute;
top: 0;
right: 0;
z-index: 10;
opacity: 0.4;
transition: all 0.6s;
}
Basic stuff I think — the sidePageOpen is a boolean state passed down, I have a button on another page that toggles this state. If anyone has any ideas/suggestions that would be brilliant and appreciated.
Remove the opacity property from sidepage class.
.sidepage {
background: white;
padding: 10px;
height: 100%;
width: 90vw;
position: absolute;
top: 0;
right: 0;
z-index: 10;
opacity: 0.4; // remove me
transition: all 0.6s;
}
The element get's added with a class of sidepage which has a opacity of 0.4, thats whats breaking the animation. Working demo here
Eventually found the solution — I had a styled <Wrapper> div created using emotion.sh styled components, I was using this to contain all of my content, not sure why but this didn't allow any animations — changing this to a simple <div> seemed to fix it.
Edit: Probably because it was recreating the Wrapper component on every state change.
I want to achieve an effect like this one
in a React webpage but without using jQuery. I've looked for alternatives to that library, but without results. I've seen a lot of similar questions, but each of them are answered using jQuery.
The effect is basically changing the color of the logo (and other elements in the page) as I scroll down through different sections.
Does anyone know a way to achieve this?
A way this could be done is by centering the logo's to their own containers dynamically, kinda like simulating position fixed, but using position absolute, so each logo is contained in their own section, and not globally like position fixed would do.
This way when you scroll to the next section, the second section covers the first section making it look like its transitioning.
I created a proof of concept here:
https://codesandbox.io/s/9k4o3zoo
NOTE: this demo is a proof of concept, it could be improved in performance by using something like request animation frame, and throttling.
Code:
class App extends React.Component {
state = {};
handleScroll = e => {
if (!this.logo1) return;
const pageY = e.pageY;
// 600 is the height of each section
this.setState(prevState => ({
y: Math.abs(pageY),
y2: Math.abs(pageY) - 600
}));
};
componentDidMount() {
window.addEventListener("scroll", this.handleScroll);
}
render() {
const { y, y2 } = this.state;
return (
<div>
<section className="first">
<h1
className="logo"
style={{ transform: `translateY(${y}px)` }}
ref={logo => {
this.logo1 = logo;
}}
>
YOUR LOGO
</h1>
</section>
<section className="second">
<h1
className="logo"
style={{ transform: `translateY(${y2}px)` }}
ref={logo => {
this.logo2 = logo;
}}
>
YOUR LOGO
</h1>
</section>
</div>
);
}
}
CSS would be:
section {
height: 600px;
width: 100%;
position: relative;
font-family: helvetica, arial;
font-size: 25px;
overflow: hidden;
}
.first {
background: salmon;
z-index: 1;
}
.first .logo {
color: black;
}
.second {
background: royalBlue;
z-index: 2;
}
.second .logo {
color: red;
}
.logo {
position: absolute;
margin: auto;
left: 0;
right: 0;
top: 0;
bottom: 0;
width: 230px;
height: 30px;
}