Here is a JSX approach (you will need object-hash for that).
The following example shows how to define different animations with respective unique ID based on transform: scale(n). For that purpose, define a function which returns the keyframes and its ID. The keyframes ID is a custom string suffixed with a hash of the function options (e.g. the scale factor).
(Be careful of CSS custom identifier, e.g. do not include a . in your ID. See MDN: < custom-ident >.)
import hash from "object-hash";
const keyFramesScale = (options = {}) => {
let { transforms, id, scale } = options;
transforms = transforms || "";
scale = scale || 1.25;
const keyFramesId = `scale${id ? "-" + id : ""}-${hash(options).substring(0, 6)}`;
const keyFrames = {
[`@keyframes ${keyFramesId}`]: {
"100%": {
transform: `scale(${scale}) ${transforms}`,
},
"0%": {
transform: `scale(1) ${transforms}`,
}
}
};
return [keyFramesId, keyFrames];
};
How to use it:
const [scaleUpId, keyFramesScaleUp] = keyFramesScale({ scale: 1.25, transforms: "rotate(-30deg)", id: "up" });
const [scaleDownId, keyFramesScaleDown] = keyFramesScale({ scale: 0.75, transforms: "rotate(-30deg)", id: "down" });
// scaleUpId = "scale-up-c61254"
// scaleDownId = "scale-down-6194d5"
// ...
<tag style={{
...keyFramesScaleUp,
...keyFramesScaleDown,
...(!hasTouchScreen && isActive && !isClicked && {
animation: `${scaleUpId} 0.5s infinite alternate linear`,
"&:hover": {
animation: "none",
},
}),
...(isClicked && {
animation: `${scaleDownId} .25s 1 linear`,
}),
}} />
Of course, you can write a more generic function that hashes the whole key frames and assign it an ID based on that.
EDIT
To concretize what has been said, here is the generic approach. We first define a generic function that takes an animation name (e.g. scale, pulse, etc.), its keyframes (which can be an object or a function), and optionally keyframes parameters and its default values.
import hash from "object-hash";
const createKeyFramesId = (id, keyFrames) => {
return `${id}-${hash(keyFrames).substring(0, 6)}`;
};
const genericKeyFrames = (name, keyFrames, defaults = {}, options = {}) => {
if (typeof keyFrames === "function") {
// The order of defaults & options is important: the latter overrides the former.
keyFrames = keyFrames({ ...defaults, ...options });
}
const keyFramesId = createKeyFramesId(name, keyFrames);
const keyFramesObject = {
[`@keyframes ${keyFramesId}`]: keyFrames
};
return [keyFramesId, keyFramesObject];
};
From now on, we can define all kind of animations. Their usage is the same as above.
export const keyFramesPulse = () =>
genericKeyFrames("pulse", {
"100%": {
opacity: "1",
},
"0%": {
opacity: "0.5",
},
});
export const keyFramesRotate = (options = {}) => {
const defaults = {
rotate: 360,
transforms: "",
};
const rotateKeyFrames = ({ rotate, transforms }) => {
return {
"100%": {
transform: `rotate(${rotate}deg) ${transforms}`,
}
}
};
return genericKeyFrames(`rotate`, rotateKeyFrames, defaults, options);
};
export const keyFramesScale = (options = {}) => {
const defaults = {
scale: 1.25,
transforms: ""
};
const scaleKeyFrames = ({ scale, transforms }) => {
return {
"100%": {
transform: `scale(${scale}) ${transforms}`,
},
"0%": {
transform: `scale(1) ${transforms}`,
}
}
};
return genericKeyFrames(`scale`, scaleKeyFrames, defaults, options);
};
What it looks like in DevTools:

$animation-id-count: 0 !default; @function animation-id{ $animation-id-count: $animation-id-count + 1; @return animation-id-#{$animation-id-count}; }, and then just use it like.class { $id: animation-id(); @keyframes #{$id}{ ...keyframes } }; animation: $id 1s infinite;. That way if you insert it anywhere else in your SCSS or move it, it will still match the right animation.!global, I used!defaultbut indeed thats wrong.