Anna Ehrnsperger
Design Code

Night Sky using fullPage.js and Framer Motion

21 Sep 20

Up was built with Create React App, fullPage.js and Framer Motion. Make sure to check out the site!

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

Create React App

I chose React over VanillaJS because I wanted to use Framer Motion, which makes animations super easy.

npx create-react-app up
  • delete some files (we only need App.js, index.css and index.js)
  • clean up App.js
import React from 'react';

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

export default App;

fullPage.js

npm install @fullpage/react-fullpage

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.

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;

Have a look at the code in detail. 💫