|
const xlinkNS = 'http://www.w3.org/1999/xlink'; |
|
const svgNS = 'http://www.w3.org/2000/svg'; |
|
const $ = document.querySelector.bind(document); |
|
const PI = Math.PI |
|
const DEG = PI / 180; |
|
const R = (a, b) => a + Math.floor(Math.random()*(b-a+1)); |
|
const pathDef = (arr, closed) => 'M' + arr.map(({x, y}) => `${x} ${y}`).join('L') + (closed ? 'Z':''); |
|
|
|
function rotate({x, y}, a) { |
|
const { sin, cos } = Math; |
|
return { |
|
x: x * cos(a) - y * sin(a), |
|
y: x * sin(a) + y * cos(a) |
|
}; |
|
} |
|
|
|
function translate({x, y}, tx, ty) { |
|
return { |
|
x: x + tx, |
|
y: y + ty |
|
} |
|
} |
|
|
|
function scale({x,y}, sx, sy) { |
|
return { |
|
x: x * sx, y: y * sy |
|
} |
|
} |
|
|
|
function mirrorX({x,y}) { |
|
return scale({x,y}, -1, 1); |
|
} |
|
|
|
function petal(w, h, N = 4) { |
|
const p = Array(N).fill(0).map((_, i) => ({ |
|
x: (i < N-1) ? (Math.random() * (w/2)) : 0, |
|
y: (i+1)/N*h}) |
|
); |
|
return [{x:0,y:0}, ...p, ...p.reverse().slice(1).map(mirrorX), {x:0,y:0}] |
|
} |
|
|
|
function flower(numLeafs) { |
|
const P = petal(25, 40, R(2, 16)).slice(0,-1); |
|
return [...Array(numLeafs).fill(0).map( |
|
(_, i) => P.map(point => rotate(point, i * DEG * 360 / numLeafs)) |
|
).flat(), {x:0, y:0}]; |
|
} |
|
|
|
|
|
for (let i = 0; i < 16; i++) { |
|
const path = document.createElementNS(svgNS, 'path'); |
|
path.setAttribute('class', 'stroke'); |
|
path.setAttribute('d', pathDef(flower(R(2,8) * 3))); |
|
path.setAttribute('id', 'flower_' + (i+1)); |
|
$('svg').appendChild(path); |
|
} |
|
|
|
const initial = document.createElementNS(svgNS, 'path'); |
|
initial.setAttribute('class', 'stroke'); |
|
initial.setAttribute('d', $('path').getAttribute('d')); |
|
initial.setAttribute('id', 'flower_initial'); |
|
$('svg').appendChild(initial); |
|
|
|
const tl = gsap.timeline({repeat: -1, paused: true }); |
|
|
|
for (let i = 2; i <= 16; i++) { |
|
tl.to('#flower_1', {morphSVG: '#flower_' + i}, '+=1') |
|
} |
|
|
|
tl.to('#flower_1', {morphSVG: '#flower_initial'}, '+=1') |
|
|
|
const first = $('path'); |
|
const pathLength = first.getTotalLength(); |
|
first.style.strokeDasharray = pathLength |
|
first.style.strokeDashoffset = pathLength |
|
first.classList.add('anim') |
|
first.addEventListener('animationend', () => { |
|
first.style.strokeDasharray = 'initial'; |
|
first.style.strokeDashoffset = 'initial'; |
|
tl.resume() |
|
}) |