Blog Aug 27, 2017 · 11 min read

Create React Video🎥

I'm excited to share with you a side project I've been working on.

You may have seen those short informational videos on Facebook, such as this one by the World Economic Forum.

I find it to be a very engaging way of presenting information. The visual components that are in the video are basic and can be mimicked with HTML and CSS. They are then reused along the video with different text.

Most video editing software has a lot of features that are not needed to accomplish this type of video. They usually have a bit of a learning curve and it's not always easy to reuse work between projects.

I decided to implement a way of coding videos using React by forking "Create React App", a project that makes it easy to develop a web page using React. While in Create React App running yarn start opens a browser tab that refreshes when you update your code, Create React Video opens a video player, built with Electron, that also reloads when you update the code.

Here's a very raw example video:

And here's the code to generate it:

import React from "react";
import ReactDOM from "react-dom";
import Gideo, { Audio, Video, animatable } from "@robo54/gideo";
import "typeface-montserrat";
import locationIcon from "./location.svg";

const colors = {
  primary: "#7c4dff",
  black: "#1a1f21",
  white: "#fff"
};

const Border = props => {
  const style = {
    border: `15px solid ${colors.primary}`,
    height: "100%",
    pointerEvents: "none",
    position: "absolute",
    width: "100%",
    zIndex: 100
  };

  return (
    <div style={style}>
      {props.children}
    </div>
  );
};

const PaddedText = props => {
  const { spacing, style, ...otherProps } = props;

  if (spacing && style.background) {
    style.paddingTop = spacing * 0.6;
    style.paddingBottom = spacing * 0.6;
    style.boxShadow = `${spacing}px 0 0 ${style.background},
      -${spacing}px 0 0 ${style.background}`;
  }

  return (
    <span style={Object.assign(style, props.style)} {...otherProps}>
      {props.children}
    </span>
  );
};

const OpeningText = animatable(props => {
  const style = {
    container: {
      alignItems: "center",
      bottom: 0,
      display: "flex",
      justifyContent: "center",
      left: `${props.animate(100, 0, {
        duration: 2,
        easing: "easeOutQuart"
      })}%`,
      overflow: "hidden",
      position: "absolute",
      right: 0,
      top: 0
    },
    content: {
      position: "absolute",
      width: props.width
    },
    text: {
      background: colors.white,
      fontSize: 46,
      lineHeight: 1.25
    }
  };

  return (
    <div style={style.container}>
      <div style={style.content}>
        <PaddedText spacing={10} style={style.text}>
          {props.children}
        </PaddedText>
      </div>
    </div>
  );
});

const StepText = animatable(props => {
  const counterSize = 55;
  const style = {
    main: {
      alignItems: "center",
      display: "flex",
      height: props.height,
      margin: "0 auto",
      width: props.width + counterSize + 15
    },
    wrapper: {
      width: "100%"
    },
    counter: {
      background: colors.primary,
      color: colors.white,
      float: "left",
      fontFamily: "Montserrat",
      fontSize: 42,
      fontWeight: 600,
      height: counterSize,
      lineHeight: `${counterSize}px`,
      paddingBottom: 2,
      textAlign: "center",
      transformOrigin: "top right",
      transform: `scale(${props.animate(0, 1, {
        duration: 0.5,
        easing: "easeOutSine"
      })})`,
      width: counterSize
    },
    container: {
      marginLeft: counterSize,
      overflow: "hidden"
    },
    content: {
      marginLeft: `${props.animate(-props.width, 0, {
        duration: 1,
        easing: "easeOutSine"
      })}px`,
      paddingLeft: 10,
      width: props.width
    },
    text: {
      background: colors.white,
      color: colors.black,
      fontSize: 25,
      lineHeight: 1.4
    },
    name: {
      background: colors.black,
      color: colors.white,
      fontSize: 27,
      lineHeight: 1.7,
      marginRight: 20
    },
    location: {
      color: colors.white,
      fontSize: 22,
      lineHeight: 1.4,
      opacity: 0.7,
      textShadow: "0 1px 2px rgba(0,0,0,.5)"
    },
    locationIcon: {
      marginRight: 4,
      width: "1rem"
    }
  };

  return (
    <div style={style.main}>
      <div style={style.wrapper}>
        <div style={style.counter}>
          {props.counter}
        </div>
        <div style={style.container}>
          <div style={style.content}>
            <PaddedText spacing={10} style={style.name}>
              {props.name}
            </PaddedText>
            <PaddedText spacing={10} style={style.location}>
              <img src={locationIcon} style={style.locationIcon} />
              {props.location}
            </PaddedText>
            <br />
            <PaddedText spacing={10} style={style.text}>
              {props.text}
            </PaddedText>
          </div>
        </div>
      </div>
    </div>
  );
});

const SourceText = props => {
  const style = {
    container: {
      bottom: 30,
      left: 40,
      position: "absolute",
      zIndex: 100
    },
    text: {
      background: colors.black,
      color: colors.white,
      fontSize: 23,
      lineHeight: 1.5
    }
  };

  return (
    <div style={style.container}>
      <PaddedText spacing={10} style={style.text}>
        {props.children}
      </PaddedText>
    </div>
  );
};

const Project = () => {
  const styles = {
    base: {
      color: colors.black,
      fontFamily: "BlinkMacSystemFont",
      fontWeight: 700
    },
    video: {
      centered: {
        position: "absolute",
        height: "100%",
        left: "50%",
        transform: "translate(-50%, 0)"
      }
    }
  };

  return (
    <Gideo width={600} height={600} duration={30} style={styles.base}>
      <Border begin={0} duration={30} />

      <Video
        begin={0}
        duration={6}
        src="/man-working-during-dawn.mp4"
        style={styles.video.centered}
      />
      <OpeningText startWith={-1} endWith={-1} width={390}>
        These
        <span style={{ color: colors.primary }}> 4 food tech startups </span>
        are worth sharing
      </OpeningText>

      <Video
        startAfter={-1}
        duration={6}
        src="/people-at-the-market.mp4"
        style={styles.video.centered}
      />
      <StepText
        startWith={-1}
        endWith={-1}
        counter={1}
        width={400}
        height="80%"
        name="Agruppa"
        location="Bogota, Colombia"
        text="Connects vendors with small farmers to establish direct supply chains and reduce costs"
      />

      <Video
        startAfter={-1}
        duration={6}
        src="/cows-in-the-field.mp4"
        style={styles.video.centered}
      />
      <StepText
        startWith={-1}
        endWith={-1}
        counter={2}
        width={390}
        height="50%"
        name="Cowlar"
        location="Islamabad, Pakistan"
        text="Produces a smart collar to help farmers improve daily herd health and boost milk yields"
      />

      <Video
        startAfter={-1}
        duration={6}
        src="/tractor-in-field.mp4"
        style={styles.video.centered}
      />
      <StepText
        startWith={-1}
        endWith={-1}
        counter={3}
        width={420}
        height="140%"
        name="GenZ Technology"
        location="Seattle, USA"
        text="Develops spray equipment that provides superior crop coverage while reducing air and soil pollution"
      />

      <Video
        startAfter={-1}
        duration={6}
        src="/greenhouse.mp4"
        startAt={7}
        playbackRate={0.6}
        style={styles.video.centered}
      />
      <StepText
        startWith={-1}
        endWith={-1}
        counter={4}
        width={465}
        height="90%"
        name="Illuminum Greenhouses"
        location="Kenya"
        text="Constructs affordable greenhouses and drip irrigation kits for small-scale farmers using solar panels and sensors"
      />

      <SourceText startWith={-1} endWidth={-1}>
        Song:  Deep Breath,<br />Mattia Vlad Morleo
      </SourceText>

      <Audio begin={0} end={30} src="/deep-breath.mp3" />
    </Gideo>
  );
};

ReactDOM.render(<Project />, document.getElementById("root"));

While the animations are very basic, there's a lot more you can do with CSS. You can also use canvas and do more advanced animations and transitions.

There are some bugs I want to tackle before open sourcing the code but hopefully I'll be able to share a first version soon!