<div id="app"></div>
@import url("https://fonts.googleapis.com/css?family=Open+Sans:400,400i,700");
body {
padding: 0;
margin: 0;
}
#app {
font-family: "Open Sans", sans-serif;
background-image: linear-gradient(to bottom right,
#ff4e4e 0 15%, #f8ff64 40% 60%, #00ce1a 85% 100%);
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
flex-wrap: nowrap;
overflow: hidden;
}
.btn-splatter {
color: #ffffff;
font-family: inherit;
font-size: 24px;
font-weight: bold;
background-color: #4487ed;
width: 18rem;
height: 6rem;
border: 0;
outline: 0;
padding: 0;
margin: 0;
flex-shrink: 0;
position: relative;
border-radius: 0.9rem;
box-shadow: 0 0.3rem 0.6rem rgba(0, 0, 0, 0.6);
text-shadow: 0 1px rgba(0, 0, 0, 0.2);
user-select: none; /* Thanks to @SplittyDev */
transition:
background-color 96ms cubic-bezier(0.18, 0.89, 0.32, 1.28),
box-shadow 96ms cubic-bezier(0.18, 0.89, 0.32, 1.28),
transform 96ms cubic-bezier(0.18, 0.89, 0.32, 1.28);
}
.btn-splatter:focus {
outline: 0;
}
.btn-splatter:active {
background-color: #2770df;
box-shadow: 0 0 0 rgba(0, 0, 0, 0.5);
transform: translateY(0.1rem) scale(0.975);
transition:
background-color 96ms ease-out,
box-shadow 96ms ease-out,
transform 96ms ease-out;
}
.splatter {
position: absolute;
top: 0;
left: 0;
}
.splatter-round, .splatter-ring, .splatter-star, .splatter-square {
position: absolute;
}
.splatter-round {
width: 1rem;
height: 1rem;
background-color: #1da5ff;
border-radius: 50%;
}
.splatter-ring {
width: 0.8rem;
height: 0.8rem;
border: solid 0.35rem #0099e6;
border-radius: 50%;
}
.splatter-star {
width: 1.8rem;
height: 1.8rem;
background-image: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" fill="%23006dd0" viewBox="0 0 24 24"%3E%3Cpath%20d%3D%22M12%2C17.27L18.18%2C21L16.54%2C13.97L22%2C9.24L14.81%2C8.62L12%2C2L9.19%2C8.62L2%2C9.24L7.45%2C13.97L5.82%2C21L12%2C17.27Z%22%3E%3C%2Fpath%3E%3C/svg%3E');
}
.splatter-square {
width: 1rem;
height: 1rem;
background-color: #0c6bc2;
}
import React, { useState, useEffect, useRef } from 'https://esm.sh/react@18.2.0'
import ReactDOM from 'https://esm.sh/react-dom@18.2.0'
import gsap from 'https://cdn.skypack.dev/gsap@3.11.4';
enum SplatterType {
SplatterRound,
SplatterRing,
SplatterStar,
SplatterSquare,
}
enum SplatterPosition {
X,
Y,
}
enum SplatterTB {
Top,
Bottom,
}
enum SplatterLR {
Left,
Right,
}
type SplatterStyle = {
top: string,
left: string,
transform: string,
}
type SplatterProps = {
type: SplatterType,
style: SplatterStyle,
}
const SplatterShapeIcon: ReactElement = (props: SplatterProps) => {
const [display, setDisplay] = useState(true);
const refSplatter = useRef(null);
const type: SplatterType = props.type;
const style: SplatterStyle = props.style;
let className = '';
if (type === SplatterType.SplatterRound) {
className = 'splatter-round';
} else if (type === SplatterType.SplatterRing) {
className = 'splatter-ring';
} else if (type === SplatterType.SplatterStar) {
className = 'splatter-star';
} else if (type === SplatterType.SplatterSquare) {
className = 'splatter-square';
}
let angle = Math.floor(Math.random() * 359) + 1;
let toX = `${Math.cos(angle) * 3}em`;
let toY = `${Math.sin(angle) * 3}em`;
useEffect(() => {
gsap.to(refSplatter.current, 0.8, {
opacity: 0,
translateX: toX,
translateY: toY,
scale: 0,
rotate: 270,
filter: 'blur(0.2rem)',
onComplete: () => {
setDisplay(false);
},
});
}, [refSplatter]);
if (!display) {
return null;
}
return (
<div ref={refSplatter} className={className} style={style}></div>
);
};
const FancyExplodingButton: ReactElement = () => {
const [splatters, setSplatters]: Array<SplatterProps> = useState([]);
const getSizeByType: number = (type: SplatterType) => {
switch (type) {
case SplatterType.SplatterRound:
case SplatterType.SplatterSquare:
return `${1 / 2}em`;
case SplatterType.SplatterRing:
return `${1.5 / 2}em`;
case SplatterType.SplatterStar:
return `${1.8 / 2}em`;
}
};
const createSplatter: void = (count: number, w: number, h: number) => {
let tmpSplatters: Array<SplatterProps> = JSON.parse(JSON.stringify(splatters));
for (let i: number = 0; i < count; i++) {
let type: SplatterType = Math.floor(Math.random() * 4);
let splatterPosition: SplatterPosition = Math.round(Math.random());
let xy: SplatterPosition = Math.round(Math.random());
let tb: SplatterTB = Math.round(Math.random());
let lr: SplatterLR = Math.round(Math.random());
let xPosition: number = (xy === SplatterPosition.X)
? Math.floor(Math.random() * w)
: (lr === SplatterLR.Left ? 0 : w);
let yPosition: number = (xy === SplatterPosition.X)
? (tb === SplatterTB.Top ? 0 : h)
: Math.floor(Math.random() * h);
let size: number = getSizeByType(type);
let scale: number = (Math.random() * 2) + 0.5;
let style: SplatterStyle = {
top: `calc(${yPosition}px - ${size})`,
left: `calc(${xPosition}px - ${size})`,
transform: `rotate(0deg) scale(${scale})`,
};
tmpSplatters.push({ type, style } as SplatterProps);
}
setSplatters(tmpSplatters);
};
const addSplatter: void = (evt) => {
let elm = evt.target;
let count = Math.floor(Math.random() * 4) + 5;
let w = elm.clientWidth;
let h = elm.clientHeight;
createSplatter(count, w, h);
};
return (
<button class="btn-splatter" onClick={addSplatter}>
PRESS ME
{splatters.map((props: SplatterProps, index: number) => {
return (
<SplatterShapeIcon
type={props.type}
style={props.style}
key={index}
/>
);
})}
</button>
);
};
ReactDOM.render(<FancyExplodingButton />, document.querySelector('#app'));
View Compiled
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.