Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <div id="app"></app>
              
            
!

CSS

              
                html, body {
  width: 100%;
  height: 100%;
  font-family:Helvetica, Arial, sans-serif;
  font-weight: 100;
  overflow: hidden;
}

#app {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.links{
  position:fixed;
  bottom:10px;
  left:10px;
  font-size:20px;
}

.links .fa{
  margin-right:5px;
  vertical-align:middle;
}

.links .linkText{
  position:relative;
  top:1px;
  vertical-align:middle;
}

.links a{
  display:block;
  text-decoration:none;
  color: #5B96FF;
  transition: 0.3s all;
}

.links a:hover{
  color:#447BA1;
}
              
            
!

JS

              
                /*
 * React Material Design Loading Spinner
 * Based off https://material.io/guidelines/components/progress-activity.html#progress-activity-types-of-indicators
 */
class LoadingSpinner extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      flipArcDirection: false,
      rotationCompletion: 0,
      arcCompletion: 0,
      rotationOffset: 0,
    };
  }

  componentWillMount() {
    const rotationDuration = this.props.rotationDuration;
    const arcDuration = this.props.arcDuration;
    let arcTimeStart = null;
    let rotationTimeStart = null;
    let flipArcDirection = false;
    let rotationOffset = 0;
    let halfCycleCounter = 0;

    const step = (timestamp) => {
      // Set starting times for future calculations
      if (!arcTimeStart) arcTimeStart = timestamp;
      if (!rotationTimeStart) rotationTimeStart = timestamp;
      
      // Get's the progress of the animations from 0-1 where 0 is the start and 1 is the end
      let rotationProgress = (timestamp - rotationTimeStart) / rotationDuration;
      let arcProgress = (timestamp - arcTimeStart) / arcDuration;
      
      // Don't let the progress be greater than 1, which is the end
      if (arcProgress > 1) arcProgress = 1;
      if (rotationProgress > 1) rotationProgress = 1;
      
      // Apply easing to the progress so it looks nicer
      // The easing is customizable, but it defaults to
      // the standard material design easing curve
      const arcCompletion = this.props.easing(arcProgress);
      const rotationCompletion = rotationProgress; // Linear easing
            
      // Set's the state variables which are used in the render function
      this.setState({
        arcCompletion,
        rotationCompletion,
        flipArcDirection,
        rotationOffset,
      });

      // Restart the rotation when it's done
      if (rotationProgress === 1) {
        rotationTimeStart = null;
      }

      // Restart the arc when it's done
      // Also flip the arc so it goes backwards next time
      if (arcProgress === 1) {
        arcTimeStart = null;
        flipArcDirection = !flipArcDirection;
        
        // Count how many half cycles we've done
        // i.e. each arc extension is a half cycle
        halfCycleCounter += 1;
        if (halfCycleCounter === 2) {
          halfCycleCounter = 0;
        }

        // Increase the rotation so it doesn't jump backwards
        // when the arc reverses
        if (halfCycleCounter === 1) {
          rotationOffset += this.props.maxArcAngle;
        }

        // Rotation offset only needs to be within 0-360
        if (rotationOffset > 360) {
          rotationOffset -= 360;
        }
      }

      this.requestId = raf(step);
    };

    // Save the request id so we can clean it up later
    // Uses raf, a polyfill for requestAnimationFrame
    // https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
    // https://www.npmjs.com/package/raf
    this.requestId = raf(step);
  }

  // Clean up our animation frame loop
  componentWillUnmount() {
    raf.cancel(this.requestId);
  }

  // https://stackoverflow.com/a/18473154
  polarToCartesian(centerX, centerY, radius, angleInDegrees) {
    const angleInRadians = ((angleInDegrees - 90) * Math.PI) / 180.0;

    return {
      x: centerX + (radius * Math.cos(angleInRadians)),
      y: centerY + (radius * Math.sin(angleInRadians)),
    };
  }

  // https://stackoverflow.com/a/18473154
  describeArc(x, y, radius, startAngle, endAngle) {
    const start = this.polarToCartesian(x, y, radius, endAngle);
    const end = this.polarToCartesian(x, y, radius, startAngle);

    const largeArcFlag = endAngle - startAngle <= 180 ? '0' : '1';

    const d = [
      'M', start.x, start.y,
      'A', radius, radius, 0, largeArcFlag, 0, end.x, end.y,
    ].join(' ');

    return d;
  }

  render() {
    const state = this.state;
    const color = this.props.color;
    const angle = state.arcCompletion * this.props.maxArcAngle;
    const rotation = (state.rotationCompletion * 360) + state.rotationOffset;
    const startAngle = state.flipArcDirection ? 0 : angle;
    const endAngle = state.flipArcDirection ? angle + this.props.minArcAngle : this.props.maxArcAngle + this.props.minArcAngle;

    // Creates the path's d attribute
    const d = this.describeArc(50, 50, 40, startAngle, endAngle);

    return (
      <svg height={this.props.size} width={this.props.size} color={color} viewBox="0 0 100 100"> 
        <g transform={`rotate(${rotation} 50 50)`}>
          <path
            fill="none"
            stroke={color}
            strokeWidth={this.props.thickness}
            strokeLinecap="round"
            d={d}
          />
        </g>
      </svg>
    );
  }
}

LoadingSpinner.defaultProps = {
  minArcAngle: 10,
  maxArcAngle: 250,
  thickness: 10,
  size: 20,
  rotationDuration: 2000,
  arcDuration: 1500,
  color: '#5B96FF',
  
  // Uses the standard material design easing curve
  // https://material.io/guidelines/motion/duration-easing.html#duration-easing-natural-easing-curves
  easing: bezierEasing(0.4, 0.0, 0.2, 1),
};

/*
 * Render the above component into the div#app
 */
ReactDOM.render(
  <section>
     <LoadingSpinner size={100} />
    <div className="links">
      <a href="https://twitter.com/benlorantfy" target="_blank"><i className="fa fa-twitter"></i><span className="linkText">Twitter</span></a>
      <a href="https://github.com/BenLorantfy/Lab/tree/master/react-material-loading-spinner" target="_blank"><i className="fa fa-github"></i><span className="linkText">Repo</span></a>
    </div>
  </section>, 
document.getElementById('app'));
              
            
!
999px

Console