React
by mamounothman
HTML
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.7.0-alpha.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.7.0-alpha.2/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.2.4/gsap.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.2.4/MotionPathPlugin.js"></script>
<div id="app"></div>
CSS
.wrap{
position: relative;
width: 375px;
height: 480px;
background: #e9e9e9;
border-radius: 5px;
}
.bubble{
position: absolute;
left: 50%;
bottom: 0;
width: 32px;
height: 32px;
border-radius: 50%;
background: #f0f;
font-size: 12px;
color: #fff;
overflow: hidden;
text-align: center;
}
Babel + JSX
const { useState, useEffect, useRef } = React;
gsap.registerPlugin(MotionPathPlugin)
const Bubble = ({ onClose, data }) => {
const pointRef = useRef(null)
useEffect(() => {
const path = []
let offsetY = 0
for(let i = 0; i < 10; i++) {
const y = offsetY - Math.floor(Math.random() * 20 + 30)
offsetY = y
path.push({ x: Math.floor(Math.random() * 40 - 20), y })
}
gsap.to(pointRef.current, 5, {
motionPath: {
path,
type: 'cubic'
},
onComplete: () => onClose()
})
return () => {}
}, [])
return (<span className="bubble" ref={pointRef}>{data.id}</span>)
}
const App = () => {
const [count, setCount] = useState(0)
const [bubbles, setBubbles] = useState([])
const handleCreate = () => {
setBubbles([...bubbles, {id: count}])
setCount(count + 1)
}
const handleClose = index => {
const newBubbles = [...bubbles]
newBubbles.splice(index, 1)
setBubbles(newBubbles)
}
return (
<div className="wrap">
{
bubbles.map((item, index) => (
<Bubble
key={item.id}
data={item}
onClose={() => handleClose(index)} />
))
}
<button type="button" onClick={handleCreate}>Click Me</button>
</div>
)
}
ReactDOM.render(<App />, document.getElementById('app'))