源码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>svg绘制彩色落叶动画特效</title>
<style>
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: local('Open Sans Regular'), local('OpenSans-Regular'), url(https://fonts.gstatic.com/s/opensans/v17/mem8YaGs126MiZpBA-UFWJ0bbck.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: local('Open Sans Regular'), local('OpenSans-Regular'), url(https://fonts.gstatic.com/s/opensans/v17/mem8YaGs126MiZpBA-UFUZ0bbck.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: local('Open Sans Regular'), local('OpenSans-Regular'), url(https://fonts.gstatic.com/s/opensans/v17/mem8YaGs126MiZpBA-UFWZ0bbck.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: local('Open Sans Regular'), local('OpenSans-Regular'), url(https://fonts.gstatic.com/s/opensans/v17/mem8YaGs126MiZpBA-UFVp0bbck.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: local('Open Sans Regular'), local('OpenSans-Regular'), url(https://fonts.gstatic.com/s/opensans/v17/mem8YaGs126MiZpBA-UFWp0bbck.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: local('Open Sans Regular'), local('OpenSans-Regular'), url(https://fonts.gstatic.com/s/opensans/v17/mem8YaGs126MiZpBA-UFW50bbck.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: local('Open Sans Regular'), local('OpenSans-Regular'), url(https://fonts.gstatic.com/s/opensans/v17/mem8YaGs126MiZpBA-UFVZ0b.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
* {
box-sizing: border-box;
}
html, body {
width: 100%;
height: 100%;
}
body {
background: #1C1F2B;
color: #fff;
margin: 0px;
font-family: 'Open Sans', sans-serif;
overflow: hidden;
}
svg {
display: block;
width: 100%;
height: 100%;
}
#storage {
position: absolute;
left: -100%;
top: -100%;
}
#leafTemplate .stem {
fill: currentColor;
}
.light-red {
fill: #e57373;
color: #d93232;
}
.red {
fill: #f44336;
color: #d2190b;
}
.dark-red {
fill: #d32f2f;
color: #962020;
}
.light-orange {
fill: #ffb74d;
color: #ff9801;
}
.orange {
fill: #ff9800;
color: #b36a00;
}
.dark-orange {
fill: #f57c00;
color: #a95500;
}
.light-yellow {
fill: #fff176;
color: #ffe92a;
}
.yellow {
fill: #ffeb3b;
color: #eed500;
}
.dark-yellow {
fill: #fbc02d;
color: #d79b04;
}
.light-green {
fill: #81c784;
color: #4dae52;
}
.green {
fill: #4caf50;
color: #357a38;
}
.dark-green {
fill: #388e3c;
color: #225725;
}
.light-purple {
fill: #ba68c8;
color: #973da7;
}
.purple {
fill: #9c27b0;
color: #641971;
}
.dark-purple {
fill: #7b1fa2;
color: #4a1362;
}
</style>
</head>
<body>
<div></div>
<script>
/* jshint esversion: 6, asi: true, boss: true */
let $mm = Object.assign((s, attrs) => {
if (s === null) { return null }
let el
if (s instanceof Element || s instanceof HTMLDocument || s instanceof Window) {
el = s
} else {
let match = s.match(/<\/?(.*?)>/)
el = match ? $mm.create(match[1], attrs) : $mm.find(s)
}
if (el === null) { return null }
$mm.merge(el, $mm.fn)
Object.defineProperty(el, 'val', {
configurable: true,
get: () => el.value,
set: v => { el.value = v }
})
Object.defineProperty(el, 'parent', {
configurable: true,
get: () => $mm(el.parentElement)
})
Object.defineProperty(el, 'next', {
configurable: true,
get: () => $mm(el.nextElementSibling),
set: v => el.appendAfter(v)
})
Object.defineProperty(el, 'prev', {
configurable: true,
get: () => $mm(el.previousElementSibling),
set: v => el.appendBefore(v)
})
Object.defineProperty(el, 'bounds', {
configurable: true,
get: () => el.getBoundingClientRect()
})
Object.defineProperty(el, 'text', {
configurable: true,
get: () => el.textContent,
set: v => { el.textContent = v }
})
Object.defineProperty(el, 'html', {
configurable: true,
get: () => el.innerHTML,
set: v => { el.innerHTML = v }
})
return el
}, {
find: s => $mm(document.querySelector(s)),
findAll: s => [...document.querySelectorAll(s)].map(e => $mm(e)),
create: (elName, attrs) => {
let el = document.createElement(elName)
if (attrs !== undefined) {
let keys = Object.keys(attrs)
let styleKey = keys.find(k => k.toLowerCase() === 'style' && $mm.isObj(attrs[k]))
let innerHtmlKey = keys.find(k => k.toLowerCase() === 'innerhtml' || k.toLowerCase() === 'html')
if (styleKey !== undefined) {
for (let k in attrs[styleKey]) {
el.style[k] = attrs[styleKey][k]
}
delete attrs[styleKey]
}
if (innerHtmlKey !== undefined) {
el.innerHTML = attrs[innerHtmlKey]
delete attrs[innerHtmlKey]
}
for (let k in attrs) {
el.setAttribute(k, attrs[k])
}
}
return $mm(el)
},
createSvg: (elName, attrs) => {
let el = document.createElementNS('http://www.w3.org/2000/svg', elName)
if (attrs !== undefined) {
let keys = Object.keys(attrs)
let styleKey = keys.find(k => k.toLowerCase() === 'style' && $mm.isObj(attrs[k]))
let innerHtmlKey = keys.find(k => k.toLowerCase() === 'innerhtml' || k.toLowerCase() === 'html')
if (styleKey !== undefined) {
for (let k in attrs[styleKey]) {
el.style[k] = attrs[styleKey][k]
}
delete attrs[styleKey]
}
if (innerHtmlKey !== undefined) {
el.innerHTML = attrs[innerHtmlKey]
delete attrs[innerHtmlKey]
}
for (let k in attrs) { el.setAttribute(k, attrs[k]) }
}
return $mm(el)
},
rand: (max = 1, min = 0) => Math.random() * (max - min) + min,
randInt: (max, min = 0) => ~~(Math.random() * (max - min) + min),
randArr: arr => arr[$mm.randInt(arr.length)],
isObj: obj => typeof obj === 'object' && obj.constructor !== RegExp && obj.constructor !== Date,
merge: (deep, ...objs) => {
if (deep !== true) { return Object.assign(deep, ...objs) }
return objs.reduce((dest, src) => {
if (Array.isArray(src)) {
return Array.isArray(dest) ? dest.concat(src) : src
}
Object.keys(src).forEach(k => {
if (src[k] && $mm.isObj(src[k])) {
$mm.merge(true, dest[k], src[k])
} else {
dest[k] = src[k]
}
})
return dest
})
},
extend: (...objs) => {
return $mm.merge($mm, ...objs)
},
round: (num, prec = 2) => {
let x = 10 ** prec
return Math.round(num * x) / x
},
dist: (p1, p2) => Math.hypot(p1.x - p2.x, p1.y - p2.y),
cssVar: (v, val) => {
if (val === undefined) {
return window.getComputedStyle(document.body).getPropertyValue(`--${v}`).trim()
}
document.documentElement.style.setProperty(`--${v}`, val)
},
array: (n, fn) => Array.from({length: n}, fn),
fn: {
find: function (s) { return $mm(this.querySelector(s)) },
findAll: function (s) { return [...this.querySelectorAll(s)].map(e => $mm(e)) },
on: function (type, ...args) {
for (let t of type.split(' ')) {
this.addEventListener(t, ...args)
}
return this
},
appendBefore: function (el) {
this.parent.insertBefore(el, this)
return this
},
appendAfter: function (el) {
this.parent.insertBefore(el, this.nextSibling)
return this
},
appendTo: function (el) {
el.append(this)
return this
},
addClass: function (c) {
this.classList.add(c)
return this
},
removeClass: function (c) {
this.classList.remove(c)
return this
},
toggleClass: function (c) {
this.classList.toggle(c)
return this
},
hasClass: function (c) {
return this.classList.contains(c)
},
attr: function (a, val) {
if (val === undefined) {
return this.getAttribute(a)
}
this.setAttribute(a, val)
return this
},
data: function (a, val) {
if (val === undefined) {
return this.getAttribute(`data-${a}`)
}
this.setAttribute(`data-${a}`, val)
return this
},
getStyle: function (...attrs) {
let style = window.getComputedStyle(this)
let ret = {}
if (attrs.length > 1) {
for (let a of attrs) {
ret[a] = style.getPropertyValue(a)
}
} else if (attrs.length === 1) {
ret = style.getPropertyValue(attrs[0])
} else {
for (let a in style) {
ret[a] = style.getPropertyValue(a)
}
}
return ret
},
serialize: function () {
let obj = {}
this.findAll('input, select, textarea').forEach(field => {
switch (field.type) {
case 'checkbox':
obj[field.name] = field.checked
break
case 'radio':
if (field.checked) {
obj[field.name] = field.value
} else if (!(field.name in obj)) {
obj[field.name] = null
}
break
default:
obj[field.name] = field.value
}
})
return obj
},
validate: function () {
let fields = this.findAll('[data-required]')
let obj = {}
fields.forEach(field => {
switch (field.type) {
case 'checkbox':
break
case 'radio':
if (field.checked) {
obj[field.name] = true
} else if (!(field.name in obj)) {
obj[field.name] = false
}
break
default:
obj[field.name] = field.value.length && field.validity.valid
}
})
let valid = Object.values(obj).every(e => e)
if (!valid) {
fields.forEach(field => field.attr('required', ''))
}
return $mm.merge({valid: valid}, obj)
},
extend: (...objs) => {
return $mm.merge($mm.fn, ...objs)
}
}
})
/* jshint esversion: 6, asi: true, boss: true */
const body = $mm('body')
const fragment = document.createDocumentFragment()
const root = $mm.createSvg('svg', {
viewBox: `0 0 ${window.innerWidth} ${window.innerHeight}`
})
fragment.append(root)
window.addEventListener('resize', () => root.attr('viewBox', `0 0 ${window.innerWidth} ${window.innerHeight}`))
let side = [-60,0]
let cBot = [side[0],70]
let cTop = [side[0],-40]
let d = `M0,100 Q${cBot} ${side} Q${cTop} 0,-100 Q${-cTop[0]},${cTop[1]} ${-side[0]},${side[1]} Q${-cBot[0]},${cBot[1]} 0,100`
function createLeaf () {
let leaf = $mm.createSvg('g')
leaf.append($mm.createSvg('path', {
style: {
transform: 'translate(0, 100px) rotate(-70deg)'
},
d: d
}))
leaf.append($mm.createSvg('path', {
style: {
transform: 'translate(100px, 100px) rotate(60deg)'
},
d: d
}))
leaf.append($mm.createSvg('path', {
style: {
transform: 'translate(0, 50px) rotate(-40deg)'
},
d: d
}))
leaf.append($mm.createSvg('path', {
style: {
transform: 'translate(100px, 50px) rotate(20deg)'
},
d: d
}))
leaf.append($mm.createSvg('path', {
style: {
transform: 'translate(50px, 0) rotate(0deg)'
},
d: d
}))
let stem = $mm.createSvg('g', { class: 'stem' })
leaf.append(stem)
stem.append($mm.createSvg('path', {
style: {
transform: 'translate(45px, 100px) rotate(5deg)'
},
d: 'M0,100 L0,-100 C20,-80 10,80 0,100'
}))
stem.append($mm.createSvg('path', {
style: {
transform: 'translate(85px, 40px) rotate(40deg) scale(.5)'
},
d: 'M0,100 L0,-100 C20,-80 10,80 0,100'
}))
stem.append($mm.createSvg('path', {
style: {
transform: 'translate(5px, 110px) rotate(-70deg) scale(.5)'
},
d: 'M0,100 L0,-100 C20,-80 10,80 0,100'
}))
return leaf
}
const storage = $mm.createSvg('svg', { id: 'storage' })
fragment.append(storage)
let leafTemplate = createLeaf()
leafTemplate.id = 'leafTemplate'
storage.append(leafTemplate)
let colors = [
'light-red', 'light-orange', 'light-yellow', 'light-green', 'light-purple',
'red', 'orange', 'yellow', 'green', 'purple',
'dark-red', 'dark-orange', 'dark-yellow', 'dark-green', 'dark-purple'
]
/*
for (let i = 3; i--;) {
for (let j = 5; j--;) {
let newLeaf = $mm(leafTemplate.cloneNode(true))
newLeaf.style.transform = `translate(${j*150+100}px, ${i*200+150}px)`
console.log(colors)
newLeaf.addClass($mm.randArr(colors))
root.append(newLeaf)
}
}
*/
let leaves = []
for (let i = 50; i--;) {
let speed = $mm.rand(.75, .15)
let x = $mm.randInt(window.innerWidth)
let y = -100 - $mm.randInt(300)
let angle = $mm.randInt(360)
let newLeaf = $mm.createSvg('use', {
href: '#leafTemplate',
class: $mm.randArr(colors),
style: {
transform: `translate(${x}px, ${y}px) rotate(${angle}deg) scale(${$mm.rand(.25, .125)})`,
'transition-timing-function': 'linear',
'transition-duration': `${(window.innerHeight - y)/speed*10}ms`
}
})
leaves.push(newLeaf)
root.append(newLeaf)
setTimeout(() => {
newLeaf.style.transform = newLeaf.style.transform.replace(/translate\((.*?),.*?\) rotate\(.*?\)/, `translate(${x + $mm.randInt((window.innerHeight - y)/50, -(window.innerHeight - y)/50)}px, ${window.innerHeight + 100}px) rotate(${angle + $mm.randInt((window.innerHeight - y)/5)}deg)`)
newLeaf.on('transitionend', resetLeaf)
}, 10)
function resetLeaf () {
newLeaf.removeEventListener('transitionend', resetLeaf)
newLeaf.style['transition-duration'] = '0ms'
setTimeout(() => {
let x = $mm.randInt(window.innerWidth)
let y = -100 - $mm.randInt(300)
let speed = $mm.rand(.75, .15)
let angle = $mm.randInt(360)
newLeaf.style.transform = `translate(${x}px, ${y}px) rotate(${angle}deg) scale(${$mm.rand(.25, .125)})`
setTimeout(() => {
newLeaf.style['transition-duration'] = `${(window.innerHeight - y)/speed*10}ms`
setTimeout(() => {
newLeaf.style.transform = newLeaf.style.transform.replace(/translate\((.*?),.*?\) rotate\(.*?\)/, `translate(${x + $mm.randInt(window.innerHeight/50, -window.innerHeight/50)}px, ${window.innerHeight + 100}px) rotate(${angle + $mm.randInt((window.innerHeight - y)/5)}deg)`)
newLeaf.on('transitionend', resetLeaf)
}, 100)
}, 100)
}, 100)
}
}
$mm('body').append(fragment)
</script>
<div style="text-align:center;margin:50px 0; font:normal 14px/24px 'MicroSoft YaHei';">
<p>适用浏览器:360、FireFox、Chrome、Opera、傲游、搜狗、世界之窗. 不支持Safari、IE8及以下浏览器。</p>
</div>
</body>
</html>