Please enable JavaScript to view this website.

Night Sky using fullPage.js and Framer Motion

This demo was built with Create React App, fullPage.js and Framer Motion.

20.09.2020

Before I start coding, I usually make a quick sketch in Figma. This site was inspired by the picture you see on the left.

Figma Preview

Create React App

Delete some files (we only need App.js, index.css and index.js) and clean up App.js.

npx create-react-app up
import React from 'react';

function App() {
return (
<div className="App">
</div>
);
}

export default App;

fullPage.js

For the snap sections to work, fullPage.js requires className="section". I also want to start with the last instead of the first section. You can simply set the active class to specify the section to be displayed first.

Gradients and shadows are always a bit tricky in CSS. Therefore I take the code from Figma. This is a simple method to display gradients correctly. For the styling in general I usually use Styled Components.

npm install @fullpage/react-fullpage
import React, { useState } from 'react';
import ReactFullpage from '@fullpage/react-fullpage';
import styled from 'styled-components';

function App() {
return (
<ReactFullpage
licenseKey='key'
scrollingSpeed={1500}
render={() => (
<ReactFullpage.Wrapper>
<StyledSectionThree className="section"></StyledSectionThree>
<StyledSectionTwo className="section"></StyledSectionTwo>
<StyledSectionOne className="section active"></StyledSectionOne>
</ReactFullpage.Wrapper>
)}
/>
);
}

const StyledSectionOne = styled.section`
background: linear-gradient(180deg, #3b5171 0%, #b1abb7 65.1%, #fee4b8 100%);
`
;

const StyledSectionTwo = styled.section`
background: linear-gradient(180deg, #162945 0%, #3b5171 100%);
`
;

const StyledSectionThree = styled.section`
background: linear-gradient(180deg, #0b192f 0%, #162945 100%);
`
;

export default App;

Moon

The moon is an SVG export from Figma. To use SVGs with Create React App you have to import it like this import { ReactComponent as Moon } from './path'. Then you can use it as a component <Moon />. As a little gimmick, I made the moon draggable. That's very easy with Framer Motion – you can simply add the drag prop.

import React, { useState } from 'react';
import ReactFullpage from '@fullpage/react-fullpage';
import styled from 'styled-components';
import { motion } from 'framer-motion';

import { ReactComponent as Moon } from './assets/moon.svg';

function App() {
return (
<ReactFullpage
licenseKey='key'
scrollingSpeed={1500}
render={() => (
<ReactFullpage.Wrapper>
<StyledSectionThree className="section"></StyledSectionThree>
<StyledSectionTwo className="section"></StyledSectionTwo>
<StyledSectionOne className="section active">
<motion.div drag className="moon-wrapper">
<Moon />
</motion.div>
</StyledSectionOne>
</ReactFullpage.Wrapper>
)}
/>
);
}

const StyledSectionOne = styled.section`
background: linear-gradient(180deg, #3b5171 0%, #b1abb7 65.1%, #fee4b8 100%);

.moon-wrapper {
position: absolute;
top: 15vh;
right: 20vw;
cursor: move;
padding: 5rem;

img {
pointer-events: none;
}
}
`
;

const StyledSectionTwo = styled.section`
background: linear-gradient(180deg, #162945 0%, #3b5171 100%);
`
;

const StyledSectionThree = styled.section`
background: linear-gradient(180deg, #0b192f 0%, #162945 100%);
`
;

export default App;

Stars

Let's add a few stars! Make a new component Star.js. This is a div that is styled as a circle. To achieve a subtle twinkle effect, the opacity alternates between 1 and 0.6. We will later use random values for scale, position and delay, so we pass them as props.

import React from 'react';
import styled from 'styled-components';
import { motion } from 'framer-motion';

const Star = ({ scale, xPos, yPos, delay }) => (
<StyledStar
initial={{ scale, x: xPos, y: yPos, opacity: 1 }}
animate={{ opacity: [1, 0.6, 1, 0.6] }}
transition={{ duration: 3, delay, repeat: Infinity }}
></StyledStar>
);

const StyledStar = styled(motion.div)`
position: absolute;
top: 0;
height: 5px;
width: 5px;
border-radius: 50%;
background: white;
`
;

export default Star;

Shooting Stars

Shooting stars are rotated lines whose width is animated from 0 to 150px. They also fade out at the end. I use variants with Framer Motion in this case, so everything is nicely structured. Position and delay are again passed as props.

import React from 'react';
import styled from 'styled-components';
import { motion } from 'framer-motion';

const ShootingStar = ({ startX, startY, delay }) => {
const shootingStar = {
start: {
width: 0,
opacity: 1,
rotateZ: 40,
x: startX,
y: startY,
},
shoot: {
width: '150px',
opacity: 0,
x: startX + 900,
y: startY + 900,
},
};

return (
<StyledShootingStar
variants={shootingStar}
initial="start"
animate="shoot"
transition={{ duration: 3, delay, repeat: Infinity }}
></StyledShootingStar>
);
};

const StyledShootingStar = styled(motion.div)`
position: absolute;
top: 0;
height: 1px;
background: white;
`
;

export default ShootingStar;

Up

Let us put it all together!

To display a large amount of stars I declare a function, which creates an array with x empty slots. Later we iterate over the array and create a star for every empty slot.

const createElements = (num) => [...Array(num)];

To position the stars randomly, there is a simple function, that multiplies a value by Math.random().

const randomize = (pos) => Math.random() * pos;
import React, { useState } from 'react';
import ReactFullpage from '@fullpage/react-fullpage';
import styled from 'styled-components';
import { motion } from 'framer-motion';

import { ReactComponent as Moon } from './assets/moon.svg';
import ShootingStar from './ShootingStar';
import Star from './Star';

function App() {

const windowSize = { x: window.innerWidth, y: window.innerHeight };
const randomize = (pos) => Math.random() * pos;
const createElements = (num) => [...Array(num)];

return (
<ReactFullpage
licenseKey='key'
scrollingSpeed={1500}
render={() => (
<ReactFullpage.Wrapper>
<StyledSectionThree className="section">
{createElements(150).map((el, i) => (
<Star
key={i}
scale={randomize(1)}
delay={randomize(1)}
xPos={randomize(windowSize.x)}
yPos={randomize(windowSize.y)}
/>
))}
</StyledSectionThree>
<StyledSectionTwo className="section">
{createElements(5).map((el, i) => (
<ShootingStar
key={i}
startX={randomize(windowSize.x)}
startY={randomize(windowSize.y)}
delay={randomize(10)}
/>
))}
{createElements(50).map((el, i) => (
<Star
key={i}
scale={randomize(1)}
delay={randomize(1)}
xPos={randomize(windowSize.x)}
yPos={randomize(windowSize.y) / 1.25}
/>
))}
</StyledSectionTwo>
<StyledSectionOne className="section active">
<motion.div drag className="moon-wrapper">
<Moon />
</motion.div>
</StyledSectionOne>
</ReactFullpage.Wrapper>
)}
/>
);
}

const StyledSectionOne = styled.section`
background: linear-gradient(180deg, #3b5171 0%, #b1abb7 65.1%, #fee4b8 100%);

.moon-wrapper {
position: absolute;
top: 15vh;
right: 20vw;
cursor: move;
padding: 5rem;

img {
pointer-events: none;
}
}
`
;

const StyledSectionTwo = styled.section`
background: linear-gradient(180deg, #162945 0%, #3b5171 100%);
`
;

const StyledSectionThree = styled.section`
background: linear-gradient(180deg, #0b192f 0%, #162945 100%);
`
;

export default App;