0

I'm trying to adapt this Codepen loading animation, which has the triad [markup, style and logic] and use it as an exportable react component.

For that I try to import css from css file, export javascript functions form js file, and render html in a <div> within my component.


This is the code I have so far:

Loading.jsx

import React, { Component } from 'react';
import './css/clock.css';
import * from './js/clock.js';

class Loading extends Component {
    render() {
        return (
            <div>
              <div className="container">
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 600">
              <title>clock coffee cup</title>
              <defs>
                <clipPath id="cupMask">
                    <path className="cupMask" d="M215.65,214.41c0,19.85,37.76,35.94,84.35,35.94s84.35-16.09,84.35-35.94H506V399H145V214.41h70.65Z" fill="#ff0d0d"/>
                </clipPath>
                <clipPath id="handleMask">
                    <path className="handleMask" fill="#4BFF00" d="M475,305c-23.7-2.4-104.6,3.9-104.6,3.9s12.1-11.9,13.9-46.2c0,0,2.3-39.9,0-48.3
                    c9.9,0,90.6,0,90.6,0V305z"/>
                </clipPath>    
              </defs>
              <g className="cupGroup">

                  <ellipse className="ripple" cx="300" cy="214.41" rx="84.35" ry="35.94" fill="rgba(0,0,0,0)" strokeLinecap="round" strokeMiterlimit="10" strokeWidth="6"/>
                  <ellipse className="ripple" cx="300" cy="214.41" rx="84.35" ry="35.94" fill="rgba(0,0,0,0)" strokeLinecap="round" strokeMiterlimit="10" strokeWidth="6"/>    
                <g clipPath="url(#cupMask)">
                      <path id="base" d="M216,214v48.7
                c0,46.4,37.8,84.4,84.2,84.4h-0.3c46.4,0,84.1-38,84.1-84.4V214" fill="none" strokeLinecap="round" strokeMiterlimit="10" strokeWidth="14"/>
                </g>
                <g clipPath="url(#handleMask)">
                  <path opacity="1" id="handle" d="M384.5,228.7c15.9,0,27.8,13.6,27.8,31.5s-14.9,30.5-30.8,30.5" fill="none" strokeLinecap="round" strokeMiterlimit="10" strokeWidth="14"/>
                </g>    
                <ellipse id="rim" cx="300" cy="214.41" rx="84.35" ry="35.94" fill="rgba(0,0,0,0)" strokeLinecap="round" strokeMiterlimit="10" strokeWidth="14"/>
              </g>
            <g className="clockGroup" opacity="1">
            <line id="bighand" fill="none" strokeWidth="14" strokeLinecap="round" strokeMiterlimit="10" x1="300" y1="263" x2="300" y2="189"/>
            <line id="littlehand" fill="none" strokeWidth="14" strokeLinecap="round" strokeMiterlimit="10" x1="300" y1="263" x2="300" y2="221"/>                 
              </g>
              <line id="table" x1="235" y1="376" x2="365" y2="376" fill="none" strokeLinecap="round" strokeMiterlimit="10" strokeWidth="14"/>                 
            </svg>


            </div>
            </div>
        );
    }
}
export default Loading; 

clock.css

body {
  background-color:#FFF9ED;
  overflow: hidden;
}

body,
html {
  height: 100%;
  width: 100%;
  margin: 0;
  padding: 0;
}
.container{
  position:absolute;
  width:600px;

}

svg{
  visibility:hidden;

}

line, ellipse, path{
  stroke:#574227;
}

clock.js

var xmlns = "http://www.w3.org/2000/svg",
  xlinkns = "http://www.w3.org/1999/xlink",
  select = function(s) {
    return document.querySelector(s);
  },
  selectAll = function(s) {
    return document.querySelectorAll(s);
  },
  container = select('.container'),
  cupGroup = select('.cupGroup'),
    littlehand = select('#littlehand'),
    bighand = select('#bighand'),
    cupColour = '#574227',
  clockColour = '#70A0A0',
    rippleColour = '#AD834E'

//center the container cos it's pretty an' that
TweenMax.set(container, {
  position: 'absolute',
  top: '50%',
  left: '50%',
  xPercent: -50,
  yPercent: -50
})
TweenMax.set('svg', {
  visibility: 'visible'
})
TweenMax.set([littlehand, bighand],{
  transformOrigin:'50% 100%'
})

export function makeAnimation(){
  var tl = new TimelineMax({delay:2, onComplete:makeAnimation});
  tl.to('#rim', 1, {
    attr:{
      ry:84.35
    },
    stroke:clockColour
  })
  .to('#base', 1, {
    y:-47.5,
    stroke:clockColour
  },'-=1')
  .to('#table', 0.8, {
    drawSVG:'40% 60%',
    alpha:0
  },'-=1')
  .to('#handle', 0.3, {
    x:-50,
    stroke:clockColour,
    ease:Power1.easeIn
  },'-=1')
  .to(cupGroup, 1, {
    y:36,
    ease:Back.easeOut
  },'-=1')
  .fromTo([bighand,littlehand], 1, {
    drawSVG:'-1% -1%',
    y:20
  },
    {
    y:0,
    stroke:clockColour,
    drawSVG:'0% 70%',
    ease:Back.easeOut

  },'-=0.6')
  /* .to([littlehand, bighand], 0.1, {
        rotation:0               
    }) */
  .addCallback(setClock,'+=0.4')

  .to([bighand], 2, {
    rotation:0,
    ease:Power1.easeInOut,
    delay:5
  })
    .to([littlehand], 2, {
    rotation:-360,
    ease:Power1.easeInOut
  },'-=2')
  .to([bighand,littlehand], 0.6, {
    drawSVG:'-1% -1%',
    y:-40,
    stroke:cupColour,
    ease:Back.easeIn
  })
  .to('#rim', 1, {
    attr:{
      ry:35.94
    },
    stroke:cupColour,
  },'-=0.6')
  .to('#base', 1, {
    y:0,
    stroke:cupColour,
  },'-=1')

  .to(cupGroup, 1, {
    y:0,
    ease:Back.easeInOut
  },'-=1')
  .to('#handle', 0.6, {
    x:0,
    stroke:cupColour,
  },'-=0.6')
  .staggerFromTo('.ripple', 3, {
    attr:{
      rx:0,
      ry:0
    },
    stroke:'#f7f7f7'
  },{
    attr:{
      rx:84,
      ry:36 
    },
    alpha:1,
    stroke:rippleColour

  },0.4,'-=0.6')
  .to('#table', 0.5, {
    drawSVG:'0% 100%',
    alpha:1
  },'-=3.4')


  tl.timeScale(1.8)  

}

//ScrubGSAPTimeline(tl);
//tl.progress(1)

export function setClock(){

    TweenMax.set([littlehand, bighand], {
      rotation:0               
  })
    //new date reference every minute
    var myDate = new Date();

    //hours minutes and seconds from Date object
    var hours = myDate.getHours();
    var minutes = myDate.getMinutes();
    var seconds = myDate.getSeconds();

    //minute position calculation
    var minuteValue = minutes*6;
    TweenMax.to(bighand,1.2, {
      shortRotation:minuteValue,
      ease:Back.easeOut.config(0.6)
    });

    //hour position calculation
    var hourValue = hours*30 +(minutes/2);         
    TweenMax.to(littlehand,1.2, {
      shortRotation:hourValue
    });



}

makeAnimation();

at index.html, I link javascript settings required from codepen page, like so:


index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, shrink-to-fit=no"
    />
    <meta name="theme-color" content="#000000" />
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <link href="//cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.min.css"rel="stylesheet">
    <link href="//cdnjs.cloudflare.com/ajax/libs/gsap/1.19.1/TweenMax.min.js">
    <link href="//s3-us-west-2.amazonaws.com/s.cdpn.io/35984/ScrubGSAPTimeline.js">
    <link href="//s3-us-west-2.amazonaws.com/s.cdpn.io/16327/DrawSVGPlugin.js?r=12">
    <link type="text/css" href="%PUBLIC_URL%/bootstrap.min.css" />
<!--     <script src="https://unpkg.com/react-media-player/dist/react-media-player.js"></script> -->

    <title>App</title>
  </head>

  <body>
<!--     <style>
    body {background-color: #C86428;
          background-image: url("bg.png");
          background-position: 0 5;
          opacity: 1.0;
          height: 120vh;}
    </style> -->
    <style>
      @import url(https://fonts.googleapis.com/css?family=Josefin+Sans:100,400);
      html, body {background:rgb(223,189,150);
                  font-family: 'Josefin Sans', sans-serif;
                  margin: 0px;
                  padding: 0px;
                  line-height:2.5em;}
      h1 {
          margin:0;
      }
    </style>

    <div id="root">
    </div>

  </body>
</html>

in my App.jsx I import the component:

import Page from './components/Page';

and render it like so:

<Route exact path='/page' render={() => (
   <Page
   />
)} />

and finally in Page.jsx I import Loading.jsx, like so:

import Loading from './Loading.jsx';

this is my project structure:

public/
      index.html
      js/
src/
   App.jsx
   index.js
   components/
             Page.jsx
             Loading.jsx
             css/
                clock.css
             js/
               clock.js

Nothing is being rendered, though. What am I doing wrong?

9
  • Can you post your App.jsx file? Commented Nov 1, 2019 at 4:25
  • it's huge. any particular section of it? Commented Nov 1, 2019 at 4:26
  • If App is your entry file (used in index.html, usually as id="root") you need to import <Loading/> somewhere. That part would help Commented Nov 1, 2019 at 4:30
  • where are you importing your Loading component ? Commented Nov 1, 2019 at 4:34
  • please refer to edit.I hope it helps. Commented Nov 1, 2019 at 4:37

3 Answers 3

1

There are a few edits you have to make.

You are using link tags to load javascript. You need to change to script tag.

<link href="//cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.min.css"rel="stylesheet">
<link href="//cdnjs.cloudflare.com/ajax/libs/gsap/1.19.1/TweenMax.min.js">

clock.js depends on the rendered dom element. You should not let the script run before the DOM is rendered. Use componentDidMount lifecycle method to load the script.

You can use dynamic imports if its a self executing script. In this case you might need to prefix all the global variables with window.. For example window.TweenMax, window.Power1, window.Back

componentDidMount(){
        import("./js/clock.js");
 }

OR

You can change the script to export a default function and run the function in componentDidMount

export default () => {
  //All the clock.js code
makeAnimation();
}

And use as

import makeAnimation from "./js/clock";

class Loading extends Component {

    componentDidMount(){
        makeAnimation();
    }

Demo using this approach

OR

Pure DOM Manipulation (In case of an external script).

componentDidMount(){
             fetch("url").then(res => res.text()).then(text => {
    const script = document.createElement("script");
    script.innerHTML = text;
    document.head.appendChild(script);
}

You can run the stack snippet below to see the animation in effect.

class Loading extends React.Component {

    componentDidMount(){
             fetch("https://codepen.io/nithinthampi/pen/JjjOQVv.js").then(res => res.text()).then(text => {
    const script = document.createElement("script");
    script.innerHTML = text;
    document.head.appendChild(script);
}
);
    }
    
    render() {
        return (
            <div>
              <div className="container">
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 600">
              <title>clock coffee cup</title>
              <defs>
                <clipPath id="cupMask">
                    <path className="cupMask" d="M215.65,214.41c0,19.85,37.76,35.94,84.35,35.94s84.35-16.09,84.35-35.94H506V399H145V214.41h70.65Z" fill="#ff0d0d"/>
                </clipPath>
                <clipPath id="handleMask">
                    <path className="handleMask" fill="#4BFF00" d="M475,305c-23.7-2.4-104.6,3.9-104.6,3.9s12.1-11.9,13.9-46.2c0,0,2.3-39.9,0-48.3
                    c9.9,0,90.6,0,90.6,0V305z"/>
                </clipPath>    
              </defs>
              <g className="cupGroup">

                  <ellipse className="ripple" cx="300" cy="214.41" rx="84.35" ry="35.94" fill="rgba(0,0,0,0)" strokeLinecap="round" strokeMiterlimit="10" strokeWidth="6"/>
                  <ellipse className="ripple" cx="300" cy="214.41" rx="84.35" ry="35.94" fill="rgba(0,0,0,0)" strokeLinecap="round" strokeMiterlimit="10" strokeWidth="6"/>    
                <g clipPath="url(#cupMask)">
                      <path id="base" d="M216,214v48.7
                c0,46.4,37.8,84.4,84.2,84.4h-0.3c46.4,0,84.1-38,84.1-84.4V214" fill="none" strokeLinecap="round" strokeMiterlimit="10" strokeWidth="14"/>
                </g>
                <g clipPath="url(#handleMask)">
                  <path opacity="1" id="handle" d="M384.5,228.7c15.9,0,27.8,13.6,27.8,31.5s-14.9,30.5-30.8,30.5" fill="none" strokeLinecap="round" strokeMiterlimit="10" strokeWidth="14"/>
                </g>    
                <ellipse id="rim" cx="300" cy="214.41" rx="84.35" ry="35.94" fill="rgba(0,0,0,0)" strokeLinecap="round" strokeMiterlimit="10" strokeWidth="14"/>
              </g>
            <g className="clockGroup" opacity="1">
            <line id="bighand" fill="none" strokeWidth="14" strokeLinecap="round" strokeMiterlimit="10" x1="300" y1="263" x2="300" y2="189"/>
            <line id="littlehand" fill="none" strokeWidth="14" strokeLinecap="round" strokeMiterlimit="10" x1="300" y1="263" x2="300" y2="221"/>                 
              </g>
              <line id="table" x1="235" y1="376" x2="365" y2="376" fill="none" strokeLinecap="round" strokeMiterlimit="10" strokeWidth="14"/>                 
            </svg>


            </div>
            </div>
        );
    }
}

ReactDOM.render(<Loading />, document.getElementById("root"));
body {
  background-color:#FFF9ED;
  overflow: hidden;
}

body,
html {
  height: 100%;
  width: 100%;
  margin: 0;
  padding: 0;
}
.container{
  position:absolute;
  width:600px;

}

svg{
  visibility:hidden;

}

line, ellipse, path{
  stroke:#574227;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<link href="//cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.min.css"rel="stylesheet">
    <script src="//cdnjs.cloudflare.com/ajax/libs/gsap/1.19.1/TweenMax.min.js"></script>
    
    <div id="root"></div>

Note that all the plugins you are using are not open-sourced.

https://codepen.io/GreenSock/full/OPqpRJ/

Sign up to request clarification or add additional context in comments.

Comments

1

This is because script clock.js excute before Loading Component mount into the dom. container = select('.container') and cupGroup = select('.cupGroup') will get null. So noting is being rendered on the page.

You should excute the script in clock.js in lifecyle componentDidMount.

clock.js

export funtion initClock() {
    // write the script in clock.js here
}

Loading.jsx

import {initClock} from '.js/clock.js'

class Loading extends Component {
    render() {}
    componentDidMount() {
        initClock();      
    }
}

Comments

0

You can import your style to page like:

import clockCSS from 'styles/App.module.css';

In the page you can assign styleclass as:

className={clockCSS.container}

In the style.css file you have to update the way of defining styleclasses:

:local(.container){
    position:absolute;
    width:600px;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.