Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React how to specify animation @keyframes and classes locally

Tags:

reactjs

function Loader() {
    var style = {
        border: '16px solid #eee',
        borderTop: '16px solid #3ae',
        borderRadius: '50%',
        width: '1cm',
        height: '1cm',
        animation: 'spin 2s linear infinite',
    }
    return (
        <div style={style}>
        <style>{`
            @keyframes spin {
                 0% { transform: rotate(0deg); }
                 100% { transform: rotate(360deg); }
            }
        `}</style>
        </div>
    )
}

Is there a way to specify @keyframes locally to the component (a component function in this example) without using an inlined string?

Also if you specify <style> inside the component render function does it have a performance penalty if there are multiple objects of the same class (or using the same @keyframes)?

The idea is to keep stuff inside <style> locally inside the component, I do not want to move it to the .css file, but at the same time I do not want hundreds of repeated class/keyframes definitions when only one is actually sufficient.

like image 259
exebook Avatar asked Jul 14 '18 12:07

exebook


3 Answers

This is not possible without writing a helper function or using some standard library that can inject keyframes, as browser options for this are still experimental and not widely supported, such as animate function.

https://developer.mozilla.org/en-US/docs/Web/API/Element/animate

When importing them from another file, such as css, or a JS file by using CSS modules, Webpack usually does the injection heavy lifting for you.

I would suggest that you either DO import the CSS file, or to check out styled components helper function for this purpose.

import React from "react";
import ReactDOM from "react-dom";
import { keyframes } from "styled-components";

function Loader() {
  var spin = keyframes`
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
`;

  var styles = {
    border: "16px solid #eee",
    borderTop: "16px solid #3ae",
    borderRadius: "50%",
    width: "1cm",
    height: "1cm",
    animation: `${spin} 2s linear infinite`
  };

  return <div style={styles} />;
}

const rootElement = document.getElementById("root");
ReactDOM.render(<Loader />, rootElement);
like image 115
Vlatko Vlahek Avatar answered Oct 20 '22 01:10

Vlatko Vlahek


You can do it basically the same way you already are, just declare a Keyframes component to neaten up your client code. Some usage:

<Keyframes name="oscillate" from={{ opacity: 0.9 }} to={{ opacity: 0.2 }} />

or

<Keyframes name="oscillate" _0={{ opacity: 0.9 }} _100={{ opacity: 0.2 }} />

You need the underscore or some other letter in front of the percentage values but it's otherwise the same. The component cycles through all its props outputting the formatted css. Using jsx, React has a special CSS object type which needs to be converted to string, but otherwise it's just a component.

import * as React from "react";

interface IProps {
  name: string;
  [key: string]: React.CSSProperties | string;
}

export const Keyframes = (props: IProps) => {
  const toCss = (cssObject: React.CSSProperties | string) =>
    typeof cssObject === "string"
      ? cssObject
      : Object.keys(cssObject).reduce((accumulator, key) => {
          const cssKey = key.replace(/[A-Z]/g, v => `-${v.toLowerCase()}`);
          const cssValue = (cssObject as any)[key].toString().replace("'", "");
          return `${accumulator}${cssKey}:${cssValue};`;
        }, "");

  return (
    <style>
      {`@keyframes ${props.name} {
        ${Object.keys(props)
          .map(key => {
            return ["from", "to"].includes(key)
              ? `${key} { ${toCss(props[key])} }`
              : /^_[0-9]+$/.test(key)
              ? `${key.replace("_", "")}% { ${toCss(props[key])} }`
              : "";
          })
          .join(" ")}
      }`}
    </style>
  );
};
like image 40
Ron Newcomb Avatar answered Oct 20 '22 00:10

Ron Newcomb


Here's how we will achieve it without adding any dependency.

{/*Your Js File Code */}

import { StrictMode } from "react";
import ReactDOM from "react-dom";
import React from "react";
import "./test.css";

class Anim extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      animationName: ""
    };
  }

  addStylesheetRules(rules) {
    var styleEl = document.createElement("style");
    document.head.appendChild(styleEl);
    var styleSheet = styleEl.sheet;
    styleSheet.insertRule(rules, 0);
  }

  clickHdl() {
    let animationName = `animation${Math.round(Math.random() * 100)}`;
    let keyframes = `
    @-webkit-keyframes ${animationName} {
        10% {-webkit-transform:translate(${Math.random() * 300}px, ${
      Math.random() * 300
    }px)} 
        90% {-webkit-transform:translate(${Math.random() * 300}px, ${
      Math.random() * 300
    }px)}
        100% {-webkit-transform:translate(${Math.random() * 300}px, ${
      Math.random() * 300
    }px)}
    }`;

    this.addStylesheetRules(keyframes);

    this.setState({
      animationName: animationName
    });
  }

  render() {
    let style = {
      animationName: this.state.animationName,
      animationTimingFunction: "ease-in-out",
      animationDuration: "0.6s",
      animationDelay: "0.0s",
      animationIterationCount: 1,
      animationDirection: "normal",
      animationFillMode: "forwards"
    };

    return (
      <div>
        <button type="button" onClick={this.clickHdl.bind(this)}>
          Animation!
        </button>
        <div className="box" style={style}></div>
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(
  <StrictMode>
    <Anim />
  </StrictMode>,
  rootElement
);


{/*Css Code test.css */}

.box {
  width: 30px;
  height: 30px;
  background: red;
  border-radius: 50%;
  cursor: pointer;
}

Demo: https://codesandbox.io/s/reverent-sun-qjo91?file=/src/index.js

like image 22
Furqan Masood Avatar answered Oct 20 '22 01:10

Furqan Masood