基于canvas制作全屏飘雪背景,2021年翻页倒计时间动画特效。适用于各种活动主题倒计时特效。两种特效结合可以自由拆分使用。
*,
*:before,
*:after {
-webkit-box-sizing: border-box;
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background: #73cdc0;
}
canvas#snow {
width: 100vw;
height: 100vh;
position: absolute;
pointer-events: none;
}
.container {
width: 600px;
text-align: center;
position: absolute;
-webkit-transform: translate(-50%, -50%);
-ms-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
top: 50%;
left: 50%;
font-family: 'Mada', sans-serif;
}
.intro {
margin: 5% auto;
color: #e16f97;
letter-spacing: 1px;
word-spacing: 12px;
}
.intro span {
font-size: 40px;
font-family: 'Limelight', cursive;
}
.countdown-wrapper {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-ms-flex-pack: distribute;
justify-content: space-around;
margin: 5% auto;
color: #fff;
}
label,
output,
.time-el span {
display: block;
}
.time-el {
position: relative;
}
.time-el:not(:last-child):after {
content: ":";
position: absolute;
right: -30px;
top: 28%;
font-size: 30px;
color: #e16f97;
}
.time-el .digit {
position: relative;
width: 100px;
height: 90px;
display: inline-block;
font-size: 50px;
line-height: 90px;
font-family: 'Limelight', cursive;
}
.inner {
height: 200%;
width: 100%;
position: absolute;
}
.time-el .top,
.time-el .bottom {
position: absolute;
height: 50%;
width: 100%;
overflow: hidden;
left: 0;
right: 0;
margin: auto;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
}
.time-el .bottom {
-webkit-transform-origin: 50% 0%;
-ms-transform-origin: 50% 0%;
transform-origin: 50% 0%;
}
.time-el label {
font-size: 10px;
letter-spacing: 1px;
color: #e16f97;
}
.current .top {
z-index: 3;
-webkit-transform-origin: 50% 100%;
-ms-transform-origin: 50% 100%;
transform-origin: 50% 100%;
-webkit-transform: perspective(150px);
transform: perspective(150px);
border-top-left-radius: 6px;
border-top-right-radius: 6px;
background: #036161;
color: #dcdcd6;
}
.next .top {
z-index: 4;
-webkit-transform-origin: 50% 0;
-ms-transform-origin: 50% 0;
transform-origin: 50% 0;
-webkit-transform: perspective(150px) rotateX(180deg);
transform: perspective(150px) rotateX(180deg);
bottom: 0;
border-bottom-left-radius: 6px;
border-bottom-right-radius: 6px;
background: teal;
}
.next .top .inner {
position: absolute;
top: -100%;
left: 0;
right: 0;
margin: auto;
}
.current .bottom {
z-index: 1;
top: 0;
border-top-left-radius: 6px;
border-top-right-radius: 6px;
background: #036161;
color: #dcdcd6;
}
.next .bottom {
z-index: 2;
bottom: 0;
border-bottom-left-radius: 6px;
border-bottom-right-radius: 6px;
background: teal;
}
.next .bottom .inner {
position: absolute;
top: -100%;
left: 0;
right: 0;
margin: auto;
}
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>新年倒计时</title>
<link type="text/css" href="css/style.css" rel="stylesheet" />
<script type="text/javascript" src="js/jquery.min.js"></script>
<script type="text/javascript" src="js/TweenMax.min.js"></script>
</head>
<body>
<div class="container">
<h1 class="intro"><span>2021</span> IS COMING IN</h1>
<div class="countdown-wrapper">
<div class="days time-el">
<output id="days">
<div class="digit">
<div class="next">
<span class="top"><span class="inner"></span></span>
<span class="bottom"><span class="inner"></span></span>
</div>
<div class="current">
<span class="top"><span class="inner"></span></span>
<span class="bottom"><span class="inner"></span></span>
</div>
</div>
</output>
<label for="days">天(S)</label>
</div>
<div class="hours time-el">
<output id="hours">
<div class="digit">
<div class="next">
<span class="top"><span class="inner"></span></span>
<span class="bottom"><span class="inner"></span></span>
</div>
<div class="current">
<span class="top"><span class="inner"></span></span>
<span class="bottom"><span class="inner"></span></span>
</div>
</div>
</output>
<label for="hours">时(S)</label>
</div>
<div class="minutes time-el">
<output id="minutes">
<div class="digit">
<div class="next">
<span class="top"><span class="inner"></span></span>
<span class="bottom"><span class="inner"></span></span>
</div>
<div class="current">
<span class="top"><span class="inner"></span></span>
<span class="bottom"><span class="inner"></span></span>
</div>
</div>
</output>
<label for="minutes">分(S)</label>
</div>
<div class="seconds time-el">
<output id="seconds">
<div class="digit">
<div class="next">
<span class="top"><span class="inner"></span></span>
<span class="bottom"><span class="inner"></span></span>
</div>
<div class="current">
<span class="top"><span class="inner"></span></span>
<span class="bottom"><span class="inner"></span></span>
</div>
</div>
</output>
<label for="seconds">秒(S)</label>
</div>
</div>
</div>
<canvas id="snow"></canvas>
</body>
</html>
let targetTime = new Date("Jan 1, 2021");
let oneSecond = 1000;
let oneMinute = oneSecond * 60;
let oneHour = oneMinute * 60;
let oneDay = oneHour * 24;
let $daysEl = $(".time-el #days");
let $hoursEl = $(".time-el #hours");
let $minutesEl = $(".time-el #minutes");
let $secondsEl = $(".time-el #seconds");
function startCountDown() {
updateTick();
let timeInterval = setInterval(updateTick, oneSecond);
function updateTick() {
let timeLeft = Date.parse(targetTime) - Date.parse(new Date());
// only allow flip if the value has changed
if (+$daysEl.find('.next .top .inner').html() !== +Math.floor(timeLeft / oneDay)) flipMe($daysEl);
if (+$hoursEl.find('.next .top .inner').html() !== +Math.floor(timeLeft % oneDay / oneHour)) flipMe($hoursEl);
if (+$minutesEl.find('.next .top .inner').html() !== +Math.floor(timeLeft % oneHour / oneMinute)) flipMe($minutesEl);
if (+$secondsEl.find('.next .top .inner').html() !== +Math.floor(timeLeft % oneMinute / oneSecond)) flipMe($secondsEl);
// update values
$daysEl.find('.next .top .inner, .current .bottom .inner').html(Math.floor(timeLeft / oneDay));
$hoursEl.find('.next .top .inner, .current .bottom .inner').html(("0" + Math.floor(timeLeft % oneDay / oneHour)).slice(-2));
$minutesEl.find('.next .top .inner, .current .bottom .inner').html(("0" + Math.floor(timeLeft % oneHour / oneMinute)).slice(-2));
$secondsEl.find('.next .top .inner, .current .bottom .inner').html(("0" + Math.floor(timeLeft % oneMinute / oneSecond)).slice(-2));
function flipMe(el) {
let tl = new TimelineMax().
to(el.find('.current .top'), 0.9, { rotationX: -180, ease: Power1.easeInOut, onComplete: function () {
$daysEl.find('.current .top .inner, .next .bottom .inner').html(Math.floor(timeLeft / oneDay));
$hoursEl.find('.current .top .inner, .next .bottom .inner').html(("0" + Math.floor(timeLeft % oneDay / oneHour)).slice(-2));
$minutesEl.find('.current .top .inner, .next .bottom .inner').html(("0" + Math.floor(timeLeft % oneHour / oneMinute)).slice(-2));
$secondsEl.find('.current .top .inner, .next .bottom .inner').html(("0" + Math.floor(timeLeft % oneMinute / oneSecond)).slice(-2));
TweenMax.set(el.find('.current .top'), { rotationX: 0 });
} }, 0).
to(el.find('.next .top'), 0.9, { rotationX: 0, ease: Power1.easeInOut, clearProps: 'all' }, 0);
}
if (timeLeft <= 0) clearInterval(timeInterval);
}
}
const LetItSnow = function () {
let canvas;
let ctx;
let durationLimit_ms;
const snowflakes = [];
const count = 150;
let paused = false;
let complete = false;
function update(time) {
snowflakes.forEach(function (el) {
el.update(time);
});
}
function resize() {
ctx.canvas.width = canvas.offsetWidth;
ctx.canvas.height = canvas.offsetHeight;
update();
}
function draw() {
ctx.clearRect(0, 0, canvas.offsetWidth, canvas.offsetHeight);
snowflakes.forEach(function (el) {
el.draw();
});
}
function loop(time) {
// if (time >= durationLimit_ms) LetItSnow.paused = true;
draw();
update(time);
if (!LetItSnow.complete) window.requestAnimationFrame(loop);
}
function addSnowflakes() {
for (var i = 0; i < count; i++) {
snowflakes.push(SnowFlake(canvas));
};
}
function events() {
window.addEventListener('resize', resize);
}
function init(canvasEl, durationInSec) {
// canvas = document.querySelector(`${canvasEl}`);
canvas = document.querySelector(canvasEl);
ctx = canvas.getContext('2d');
durationLimit_ms = durationInSec * 1000;
addSnowflakes();
events();
loop();
resize();
}
return {
init: init,
paused: paused,
complete: complete };
}();
function SnowFlake(canvEl) {
const canvas = canvEl;
const ctx = canvas.getContext('2d');
function random(min, max) {
return min + Math.random() * (max - min);
}
var x = random(0, canvas.offsetWidth);
var y = random(-canvas.offsetHeight, 0);
var ff = random(0.5, 1);
var fill = 'rgba(255, 255, 255, ' + ff + ')';
const radius = random(0.25, 7.0);
var i = 0;
var easeDuration_ms = 5 * 60;
var gravity = 2;
var angle = random(-1, 1);
var density = random(0, 1000);
var speed = 0;
var wind = 0;
function draw() {
ctx.beginPath();
ctx.arc(x, y, radius, 0, 2 * Math.PI);
ctx.fillStyle = fill;
ctx.fill();
ctx.closePath();
}
function animateSnowFall(time) {
const hitGround = y > canvas.offsetHeight;
angle += 0.02;
if (hitGround) {
y = 0;
x = random(0, canvas.offsetWidth);
} else {
y += speed;
x += wind;
if (LetItSnow.paused) {
slowDownAndPause(speed, wind);
} else {
// creating a curved movement and speed for diffrent snowflakes
speed = 0.15 * (Math.cos(angle + density) + 1 + radius * 3);
wind = 0.5 * (Math.sin(angle) * 2);
}
}
}
function slowDownAndPause() {
if (i < easeDuration_ms) {
// simulating easeOutSine
speed = speed - speed * Math.sin(i / easeDuration_ms * (Math.PI / 2));
wind = wind - wind * Math.sin(i / easeDuration_ms * (Math.PI / 2));
i++;
} else {
LetItSnow.complete = true;
}
}
function update(time) {
animateSnowFall(time);
}
return {
update: update,
draw: draw,
slowDownAndPause: slowDownAndPause };
}
$(function () {
startCountDown();
LetItSnow.init('#snow');
});