小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.16.1/TweenMax.min.js"></script>
<canvas id="canvas-number"></canvas>
<canvas id="canvas-dots"></canvas>
@font-face {
font-family: 'Lato';
font-style: normal;
font-weight: 900;
src: url(https://fonts.gstatic.com/s/lato/v20/S6u9w4BMUTPHh50XSwiPHA.ttf) format('truetype');
}
body {
background-color: #24282f;
margin: 0;
padding: 0;
}
canvas {
position: absolute;
top: 0;
left: 0;
}
#canvas-number {
width: 680px;
height: 420px;
}
/*
Desc: Define inital variables
*/
var numberStage,
numberStageCtx,
numberStageWidth = 680,
numberStageHeight = 420,
numberOffsetX,
numberOffsetY,
stage,
stageCtx,
stageWidth = window.innerWidth,
stageHeight = window.innerHeight,
stageCenterX = stageWidth/2,
stageCenterY = stageHeight/2,
countdownFrom = 10,
countdownTimer = 2800,
countdownRunning = true,
number,
dots = [],
numberPixelCoordinates,
circleRadius = 2,
colors = ['61, 207, 236', '255, 244, 174', '255, 211, 218', '151, 211, 226'];
/*
Desc: Init canvases & Number text
*/
function init() {
// Init stage which will have numbers
numberStage = document.getElementById("canvas-number");
numberStageCtx = numberStage.getContext('2d');
// Set the canvas to width and height of the window
numberStage.width = numberStageWidth;
numberStage.height = numberStageHeight;
// Init Stage which will have dots
stage = document.getElementById("canvas-dots");
stageCtx = stage.getContext('2d');
stage.width = stageWidth;
stage.height = stageHeight;
// Create offset so text appears in middle of screen
numberOffsetX = (stageWidth - numberStageWidth) / 2;
numberOffsetY = (stageHeight - numberStageHeight) / 2;
}
init();
/*
Desc: Dot object
*/
function Dot(x, y, color, alpha) {
var _this = this;
_this.x = x;
_this.y = y;
_this.color = color;
_this.alpha = alpha;
this.draw = function() {
stageCtx.beginPath();
stageCtx.arc(_this.x, _this.y, circleRadius, 0, 2*Math.PI, false);
stageCtx.fillStyle = 'rgba(' + _this.color + ', ' + _this.alpha + ')';
stageCtx.fill();
}
}
/*
Desc: Create a certain amount of dots
*/
for (var i = 0; i < 2240; i++) {
// Create a dot
var dot = new Dot(randomNumber(0, stageWidth), randomNumber(0, stageHeight), colors[randomNumber(1, colors.length)], .3);
// Push to into an array of dots
dots.push(dot);
// Animate dots
tweenDots(dot, '', 'space');
}
/*
Desc: Countdown
*/
function countdown() {
// Send number to be drawn
drawNumber(countdownFrom.toString());
// When we hit zero stop countdown
if (countdownFrom === 0) {
countdownRunning = false;
// Now that countdowns finised show the text Go
drawNumber('GO');
}
// Decrement number down
countdownFrom--;
}
countdown();
/*
Desc: Redraw loops
*/
function loop() {
stageCtx.clearRect(0,0,stageWidth, stageHeight);
for(var i = 0; i < dots.length; i++) {
dots[i].draw(stageCtx);
}
requestAnimationFrame(loop);
}
loop();
/*
Desc: Draw number
*/
function drawNumber(num) {
// Create a number on a seperate canvas
// Use a seperate canvas thats smaller so we have less data to loop over when using getImagedata()
// Clear stage of previous numbers
numberStageCtx.clearRect(0,0,numberStageWidth, numberStageHeight);
numberStageCtx.fillStyle = "#24282f";
numberStageCtx.textAlign = 'center';
numberStageCtx.font = "bold 418px Lato";
numberStageCtx.fillText(num, 340, 400);
var ctx = document.getElementById('canvas-number').getContext('2d');
// getImageData(x, y, width, height)
// note: is an exspenisve function, so make sure canvas is small as possible for what you grab
// Returns 1 Dimensional array of pixel color value chanels
// Red, blue, green, alpha chanel of single pixel
// First chanel is red
var imageData = ctx.getImageData(0,0,numberStageWidth,numberStageHeight).data;
// Clear number coordinated
numberPixelCoordinates = [];
// i is equal to total image data(eg: 480,000)
// run while i is greater or equal to 0
// every time we run it minus 4 from i. Do this because each pixel has 4 chanels & we are only interested in individual pixels
for (var i = imageData.length; i >= 0; i -= 4) {
// If not an empty pixel
if (imageData[i] !== 0) {
// i represents the position in the array a red pixel was found
// (i / 4 ) and percentage by width of canvas
// Need to divide i by 4 because it has 4 values and you need its orginal position
// Then you need to percentage it by the width(600) because each row contains 600 pixels and you need its relative position in that row
var x = (i / 4) % numberStageWidth;
// (i divide by width) then divide by 4
// Divide by width(600) first so you get the rows of pixels that make up the canvas. Then divide by 4 to get its postion within the row
var y = Math.floor(Math.floor(i/numberStageWidth)/4);
// If position exists and number is divisble by circle plus a pixel gap then add cordinates to array. So circles do not overlap
if((x && x%(circleRadius * 2 + 3) == 0) && (y && y%(circleRadius * 2 + 3) == 0)) {
// Push object to numberPixels array with x and y coordinates
numberPixelCoordinates.push({x: x, y: y});
}
}
}
formNumber();
}
/*
Desc: Form number
*/
function formNumber() {
for (var i = 0; i < numberPixelCoordinates.length; i++) {
// Loop out as many coordionates as we need & pass dots in to animate
tweenDots(dots[i], numberPixelCoordinates[i], '');
}
// Break number apart
if (countdownRunning && countdownFrom > 0) {
setTimeout(function() {
breakNumber();
}, countdownTimer);
}
}
function breakNumber() {
for (var i = 0; i < numberPixelCoordinates.length; i++) {
tweenDots(dots[i], '', 'space');
}
if (countdownRunning) {
// Build next number
setTimeout(function() {
countdown();
}, countdownTimer);
}
}
/*
Desc: Animate dots
*/
function tweenDots(dot, pos, type) {
// Move dots around canvas randomly
if (type === 'space') {
// Tween dot to coordinate to form number
TweenMax.to(dot, (3 + Math.round(Math.random() * 100) / 100), {
x: randomNumber(0, stageWidth),
y: randomNumber(0, stageHeight),
alpha: 0.3,
ease: Cubic.easeInOut,
onComplete: function() {
tweenDots(dot, '', 'space');
}
});
} else {
// Tween dot to coordinate to form number
TweenMax.to(dot, (1.5 + Math.round(Math.random() * 100) / 100), {
x: (pos.x + numberOffsetX),
y: (pos.y + numberOffsetY),
delay: 0,
alpha: 1,
ease: Cubic.easeInOut,
onComplete: function() {
}
});
}
}
/*
Desc: Get a random number
*/
function randomNumber(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}