文章目录
- canvas曲线运动&阳光下的泡沫效果&泡沫曲线运动&矩阵数字雨&烟花效果&方块旋转
- 曲线运动
- 勾股定理
- 三角函数
- 什么是弧度
- 角度转弧度
- 弧度转角度
- 三角函数图像
- 与canvas结合
- 曲线运动案例代码
- 阳光下的泡沫效果
- 结合canvas实现气泡效果
- 效果图
- 完整代码
- 泡沫曲线运动
- 效果图
- 完整代码
- 矩阵数字雨
- 效果图
- 完整代码
- 环形方块旋转
- 效果图
- 完整代码
- canvas烟花效果
- 效果图
- 完整代码
- canvas制作马赛克&飞鸟动画&小球拖拽动画
- canvas实现移动端刮刮卡
- 带你使用canvas实现时钟
- 后记
canvas曲线运动&阳光下的泡沫效果&泡沫曲线运动&矩阵数字雨&烟花效果&方块旋转
曲线运动
曲线运动
勾股定理
a * a + b*b =c*c
三角函数
正弦 : sin
余弦 : cos
正切 : tan
余切 : cot
正弦定理
a/sinA = b/sinB =c/sinC = 2r(r为外接圆半径)
余弦定理
cosA = b*b + c*c - a*a / 2bc
cosB = c*c + a*a - b*b / 2ca
cosC = a*a + b*b - c*c / 2ab
什么是弧度
一个角度到底代表多少弧度:这个角度所包含的外接圆的弧长/外接圆的半径
360 角度 = 2*PI*r/r 弧度(360角度 = 2*PI 弧度)
===> (单位换算)
1角度 = PI/180 弧度
1弧度 = 180/PI 角度
角度转弧度
弧度值 = 角度值*PI/180
弧度转角度
角度值 = 弧度值*180/PI
三角函数图像
与canvas结合
人眼能接收的最好频率为一秒钟60次,这样的体验是比较好的
曲线运动案例代码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<style type="text/css">
*{
margin: 0;
padding: 0;
}
#test{
position: absolute;
left: 200px;
top: 300px;
width: 10px;
height: 10px;
background: black;
}
.box{
position: absolute;
border: 1px solid;
}
</style>
</head>
<body>
<div id="test"></div>
</body>
<script type="text/javascript">
window.onload=function(){
var testNode = document.querySelector("#test");
var startX = testNode.offsetLeft;
var startY = testNode.offsetTop;
//角度
var deg =0;
var step = 100;
setInterval(function(){
deg++;
testNode.style.left = startX + (deg*Math.PI/180)*step/2 +'px';
testNode.style.top = startY + Math.tan( deg*Math.PI/180 )*step*2+"px";
var boxNode = document.createElement("div");
boxNode.classList.add("box");
boxNode.style.left=testNode.offsetLeft+"px";
boxNode.style.top=testNode.offsetTop+"px";
document.body.appendChild(boxNode);
},1000/60)
}
</script>
</html>
阳光下的泡沫效果
结合canvas实现气泡效果
第二个循环定时器:
维护一个数组(随机圆的信息)
圆心
半径
rgba值
初始位置
波峰 波谷的值
度数
第一个循环定时器:
在canvas上实现动画
第一个for循环(canvas上需要动画的元素不止一个)
将随机圆数组中需要动画的参数拿出来进行平滑的累加
第一个for循环(canvas上需要动画的元素不止一个)
使用canvas api进行绘制
效果图
完整代码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<style type="text/css">
*{
margin: 0;
padding: 0;
}
body{
background: pink;
}
canvas{
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
background: white;
}
</style>
</head>
<body>
<canvas width="400" height="400"></canvas>
</body>
<script type="text/javascript">
window.onload=function(){
var oc = document.querySelector("canvas");
if(oc.getContext){
var ctx = oc.getContext("2d");
var arr=[];
//将数组中的圆绘制到画布上
setInterval(function(){
ctx.clearRect(0,0,oc.width,oc.height);
//动画
for(var i=0;i<arr.length;i++){
if(arr[i].alp<=0){
arr.splice(i,1);
}
arr[i].r++;
arr[i].alp-=0.01;
}
//绘制
for(var i=0;i<arr.length;i++){
ctx.save();
ctx.fillStyle="rgba("+arr[i].red+","+arr[i].green+","+arr[i].blue+","+arr[i].alp+")";
ctx.beginPath();
ctx.arc(arr[i].x,arr[i].y,arr[i].r,0,2*Math.PI);
ctx.fill();
ctx.restore();
}
},1000/60)
//往arr中注入随机圆的信息
setInterval(function(){
var x = Math.random()*oc.width;
var y = Math.random()*oc.height;
var r =10;
var red = Math.round(Math.random()*255);
var green = Math.round(Math.random()*255);
var blue = Math.round(Math.random()*255);
var alp = 1;
arr.push({
x:x,
y:y,
r:r,
red:red,
green:green,
blue:blue,
alp:alp
})
},50)
}
}
</script>
</html>
泡沫曲线运动
效果图
完整代码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<style type="text/css">
*{
margin: 0;
padding: 0;
}
body{
background: pink;
}
canvas{
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
background: white;
}
</style>
</head>
<body>
<canvas width="150" height="400"></canvas>
</body>
<script type="text/javascript">
window.onload=function(){
var oc = document.querySelector("canvas");
if(oc.getContext){
var ctx = oc.getContext("2d");
var arr=[];
//将数组中的圆绘制到画布上
setInterval(function(){
console.log(arr)
ctx.clearRect(0,0,oc.width,oc.height);
//动画
for(var i=0;i<arr.length;i++){
arr[i].deg+=5;
arr[i].x = arr[i].startX + Math.sin( arr[i].deg*Math.PI/180 )*arr[i].step*2;
arr[i].y = arr[i].startY - (arr[i].deg*Math.PI/180)*arr[i].step ;
if(arr[i].y <=50){
arr.splice(i,1)
}
}
//绘制
for(var i=0;i<arr.length;i++){
ctx.save();
ctx.fillStyle="rgba("+arr[i].red+","+arr[i].green+","+arr[i].blue+","+arr[i].alp+")";
ctx.beginPath();
ctx.arc(arr[i].x,arr[i].y,arr[i].r,0,2*Math.PI);
ctx.fill();
ctx.restore();
}
},1000/60)
//往arr中注入随机圆的信息
setInterval(function(){
var r =Math.random()*6+2;
var x = Math.random()*oc.width;
var y = oc.height - r;
var red = Math.round(Math.random()*255);
var green = Math.round(Math.random()*255);
var blue = Math.round(Math.random()*255);
var alp = 1;
var deg =0;
var startX = x;
var startY = y;
//曲线的运动形式
var step =Math.random()*20+10;
arr.push({
x:x,
y:y,
r:r,
red:red,
green:green,
blue:blue,
alp:alp,
deg:deg,
startX:startX,
startY:startY,
step:step
})
},50)
}
}
</script>
</html>
矩阵数字雨
效果图
完整代码
CSS
@charset "utf-8";
html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td{margin:0;padding:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent}
body{line-height:1}
#iframe-wrap{height:100%;overflow:visible;position:relative;top:54px;z-index:50}
.tablet-width{height:960px!important;margin:0 auto;padding:175px 100px 115px 100px;width:785px;margin-top:40px;background:url(../i/bg-mob.png) no-repeat 0 0}
.tablet-width iframe{height:960px!important}
.mobile-width{height:703px!important;margin:0 auto;padding:165px 115px 100px 100px;width:1041px;margin-top:40px;background:url(../i/bg-mob.png) no-repeat 0 -1249px}
.mobile-width iframe{height:704px!important}
.mobile-width-2{height:417px!important;margin:0 auto;padding:125px 25px 159px 25px;width:337px;margin-top:40px;background:url(../i/bg-mob.png) no-repeat 0 -2217px}
.mobile-width-2 iframe{height:416px!important}
.mobile-width-3{height:256px!important;margin:0 auto;padding:45px 115px 69px 105px;width:497px;margin-top:40px;background:url(../i/bg-mob.png) no-repeat -387px -2217px}
.mobile-width-3 iframe{height:256px!important}
#by{overflow-y:hidden}
HTML&JavaScript
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0">
<title>矩阵数字雨canvas动效</title>
<link href="css/demo.css" rel="stylesheet" media="all" />
<!--[if IE]>
<style type="text/css">
li.remove_frame a {
padding-top: 5px;
background-position: 0px -3px;
}
</style>
<![endif]-->
<script type="text/javascript" src="js/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
function fixHeight() {
var headerHeight = $("#switcher").height();
$("#iframe").attr("height", $(window).height()-54+ "px");
}
$(window).resize(function () {
fixHeight();
}).resize();
$('.icon-monitor').addClass('active');
$(".icon-mobile-3").click(function () {
$("#by").css("overflow-y", "auto");
$('#iframe-wrap').removeClass().addClass('mobile-width-3');
$('.icon-tablet,.icon-mobile-1,.icon-monitor,.icon-mobile-2,.icon-mobile-3').removeClass('active');
$(this).addClass('active');
return false;
});
$(".icon-mobile-2").click(function () {
$("#by").css("overflow-y", "auto");
$('#iframe-wrap').removeClass().addClass('mobile-width-2');
$('.icon-tablet,.icon-mobile-1,.icon-monitor,.icon-mobile-2,.icon-mobile-3').removeClass('active');
$(this).addClass('active');
return false;
});
$(".icon-mobile-1").click(function () {
$("#by").css("overflow-y", "auto");
$('#iframe-wrap').removeClass().addClass('mobile-width');
$('.icon-tablet,.icon-mobile,.icon-monitor,.icon-mobile-2,.icon-mobile-3').removeClass('active');
$(this).addClass('active');
return false;
});
$(".icon-tablet").click(function () {
$("#by").css("overflow-y", "auto");
$('#iframe-wrap').removeClass().addClass('tablet-width');
$('.icon-tablet,.icon-mobile-1,.icon-monitor,.icon-mobile-2,.icon-mobile-3').removeClass('active');
$(this).addClass('active');
return false;
});
$(".icon-monitor").click(function () {
$("#by").css("overflow-y", "hidden");
$('#iframe-wrap').removeClass().addClass('full-width');
$('.icon-tablet,.icon-mobile-1,.icon-monitor,.icon-mobile-2,.icon-mobile-3').removeClass('active');
$(this).addClass('active');
return false;
});
});
</script>
<script type="text/javascript">
function Responsive($a) {
if ($a == true) $("#Device").css("opacity", "100");
if ($a == false) $("#Device").css("opacity", "0");
$('#iframe-wrap').removeClass().addClass('full-width');
$('.icon-tablet,.icon-mobile-1,.icon-monitor,.icon-mobile-2,.icon-mobile-3').removeClass('active');
$(this).addClass('active');
return false;
};
</script>
</head>
<body id="by">
<div id="iframe-wrap">
<iframe id="iframe" src="https://www.17sucai.com/preview/1424582/2020-03-29/jz/index.html" frameborder="0" width="100%"></iframe>
</div>
</body>
</html>
环形方块旋转
效果图
完整代码
HTML
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="UTF-8">
<title>HTML5 Canvas 3D环形方块翻转动画DEMO演示</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<canvas class="zdog-canvas" width="480" height="480"></canvas>
<script src='js/zdog.dist.js'></script>
<script src="js/script.js"></script>
<div style="text-align:center;clear:both;">
<script src="/gg_bd_ad_720x90.js" type="text/javascript"></script>
<script src="/follow.js" type="text/javascript"></script>
</div>
</body>
</html>
JavaScript
https://zzz.dog/
<script src='js/zdog.dist.js'></script>
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory();
} else {
// browser global
root.Zdog = factory();
}
}( this, function factory() {
var Zdog = {};
Zdog.TAU = Math.PI * 2;
Zdog.extend = function( a, b ) {
for ( var prop in b ) {
a[ prop ] = b[ prop ];
}
return a;
};
Zdog.lerp = function( a, b, alpha ) {
return ( b - a ) * alpha + a;
};
Zdog.modulo = function( num, div ) {
return ( ( num % div ) + div ) % div;
};
var powerMultipliers = {
2: function( a ) {
return a * a;
},
3: function( a ) {
return a * a * a;
},
4: function( a ) {
return a * a * a * a;
},
5: function( a ) {
return a * a * a * a * a;
},
};
Zdog.easeInOut = function( alpha, power ) {
if ( power == 1 ) {
return alpha;
}
alpha = Math.max( 0, Math.min( 1, alpha ) );
var isFirstHalf = alpha < 0.5;
var slope = isFirstHalf ? alpha : 1 - alpha;
slope /= 0.5;
// make easing steeper with more multiples
var powerMultiplier = powerMultipliers[ power ] || powerMultipliers[2];
var curve = powerMultiplier( slope );
curve /= 2;
return isFirstHalf ? curve : 1 - curve;
};
return Zdog;
}));
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory();
} else {
// browser global
root.Zdog.CanvasRenderer = factory();
}
}( this, function factory() {
var CanvasRenderer = { isCanvas: true };
CanvasRenderer.begin = function( ctx ) {
ctx.beginPath();
};
CanvasRenderer.move = function( ctx, elem, point ) {
ctx.moveTo( point.x, point.y );
};
CanvasRenderer.line = function( ctx, elem, point ) {
ctx.lineTo( point.x, point.y );
};
CanvasRenderer.bezier = function( ctx, elem, cp0, cp1, end ) {
ctx.bezierCurveTo( cp0.x, cp0.y, cp1.x, cp1.y, end.x, end.y );
};
CanvasRenderer.closePath = function( ctx ) {
ctx.closePath();
};
CanvasRenderer.setPath = function() {};
CanvasRenderer.renderPath = function( ctx, elem, pathCommands, isClosed ) {
this.begin( ctx, elem );
pathCommands.forEach( function( command ) {
command.render( ctx, elem, CanvasRenderer );
});
if ( isClosed ) {
this.closePath( ctx, elem );
}
};
CanvasRenderer.stroke = function( ctx, elem, isStroke, color, lineWidth ) {
if ( !isStroke ) {
return;
}
ctx.strokeStyle = color;
ctx.lineWidth = lineWidth;
ctx.stroke();
};
CanvasRenderer.fill = function( ctx, elem, isFill, color ) {
if ( !isFill ) {
return;
}
ctx.fillStyle = color;
ctx.fill();
};
CanvasRenderer.end = function() {};
return CanvasRenderer;
}));
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory();
} else {
// browser global
root.Zdog.SvgRenderer = factory();
}
}( this, function factory() {
var SvgRenderer = { isSvg: true };
// round path coordinates to 3 decimals
var round = SvgRenderer.round = function( num ) {
return Math.round( num * 1000 ) / 1000;
};
function getPointString( point ) {
return round( point.x ) + ',' + round( point.y ) + ' ';
}
SvgRenderer.begin = function() {};
SvgRenderer.move = function( svg, elem, point ) {
return 'M' + getPointString( point );
};
SvgRenderer.line = function( svg, elem, point ) {
return 'L' + getPointString( point );
};
SvgRenderer.bezier = function( svg, elem, cp0, cp1, end ) {
return 'C' + getPointString( cp0 ) + getPointString( cp1 ) +
getPointString( end );
};
SvgRenderer.closePath = function() {
return 'Z';
};
SvgRenderer.setPath = function( svg, elem, pathValue ) {
elem.setAttribute( 'd', pathValue );
};
SvgRenderer.renderPath = function( svg, elem, pathCommands, isClosed ) {
var pathValue = '';
pathCommands.forEach( function( command ) {
pathValue += command.render( svg, elem, SvgRenderer );
});
if ( isClosed ) {
pathValue += this.closePath( svg, elem );
}
this.setPath( svg, elem, pathValue );
};
SvgRenderer.stroke = function( svg, elem, isStroke, color, lineWidth ) {
if ( !isStroke ) {
return;
}
elem.setAttribute( 'stroke', color );
elem.setAttribute( 'stroke-width', lineWidth );
};
SvgRenderer.fill = function( svg, elem, isFill, color ) {
var fillColor = isFill ? color : 'none';
elem.setAttribute( 'fill', fillColor );
};
SvgRenderer.end = function( svg, elem ) {
svg.appendChild( elem );
};
return SvgRenderer;
}));
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./boilerplate') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Vector = factory( Zdog );
}
}( this, function factory( utils ) {
function Vector( position ) {
this.set( position );
}
var TAU = utils.TAU;
// 'pos' = 'position'
Vector.prototype.set = function( pos ) {
this.x = pos && pos.x || 0;
this.y = pos && pos.y || 0;
this.z = pos && pos.z || 0;
return this;
};
// set coordinates without sanitizing
// vec.write({ y: 2 }) only sets y coord
Vector.prototype.write = function( pos ) {
if ( !pos ) {
return this;
}
this.x = pos.x != undefined ? pos.x : this.x;
this.y = pos.y != undefined ? pos.y : this.y;
this.z = pos.z != undefined ? pos.z : this.z;
return this;
};
Vector.prototype.rotate = function( rotation ) {
if ( !rotation ) {
return;
}
this.rotateZ( rotation.z );
this.rotateY( rotation.y );
this.rotateX( rotation.x );
return this;
};
Vector.prototype.rotateZ = function( angle ) {
rotateProperty( this, angle, 'x', 'y' );
};
Vector.prototype.rotateX = function( angle ) {
rotateProperty( this, angle, 'y', 'z' );
};
Vector.prototype.rotateY = function( angle ) {
rotateProperty( this, angle, 'x', 'z' );
};
function rotateProperty( vec, angle, propA, propB ) {
if ( !angle || angle % TAU === 0 ) {
return;
}
var cos = Math.cos( angle );
var sin = Math.sin( angle );
var a = vec[ propA ];
var b = vec[ propB ];
vec[ propA ] = a*cos - b*sin;
vec[ propB ] = b*cos + a*sin;
}
Vector.prototype.isSame = function( pos ) {
if ( !pos ) {
return false;
}
return this.x === pos.x && this.y === pos.y && this.z === pos.z;
};
Vector.prototype.add = function( pos ) {
if ( !pos ) {
return this;
}
this.x += pos.x || 0;
this.y += pos.y || 0;
this.z += pos.z || 0;
return this;
};
Vector.prototype.subtract = function( pos ) {
if ( !pos ) {
return this;
}
this.x -= pos.x || 0;
this.y -= pos.y || 0;
this.z -= pos.z || 0;
return this;
};
Vector.prototype.multiply = function( pos ) {
if ( pos == undefined ) {
return this;
}
// multiple all values by same number
if ( typeof pos == 'number' ) {
this.x *= pos;
this.y *= pos;
this.z *= pos;
} else {
// multiply object
this.x *= pos.x != undefined ? pos.x : 1;
this.y *= pos.y != undefined ? pos.y : 1;
this.z *= pos.z != undefined ? pos.z : 1;
}
return this;
};
Vector.prototype.transform = function( translation, rotation, scale ) {
this.multiply( scale );
this.rotate( rotation );
this.add( translation );
return this;
};
Vector.prototype.lerp = function( pos, alpha ) {
this.x = utils.lerp( this.x, pos.x || 0, alpha );
this.y = utils.lerp( this.y, pos.y || 0, alpha );
this.z = utils.lerp( this.z, pos.z || 0, alpha );
return this;
};
Vector.prototype.magnitude = function() {
var sum = this.x*this.x + this.y*this.y + this.z*this.z;
return getMagnitudeSqrt( sum );
};
function getMagnitudeSqrt( sum ) {
// PERF: check if sum ~= 1 and skip sqrt
if ( Math.abs( sum - 1 ) < 0.00000001 ) {
return 1;
}
return Math.sqrt( sum );
}
Vector.prototype.magnitude2d = function() {
var sum = this.x*this.x + this.y*this.y;
return getMagnitudeSqrt( sum );
};
Vector.prototype.copy = function() {
return new Vector( this );
};
return Vector;
}));
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./boilerplate'), require('./vector'),
require('./canvas-renderer'), require('./svg-renderer') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Anchor = factory( Zdog, Zdog.Vector, Zdog.CanvasRenderer,
Zdog.SvgRenderer );
}
}( this, function factory( utils, Vector, CanvasRenderer, SvgRenderer ) {
var TAU = utils.TAU;
var onePoint = { x: 1, y: 1, z: 1 };
function Anchor( options ) {
this.create( options || {} );
}
Anchor.prototype.create = function( options ) {
this.children = [];
// set defaults & options
utils.extend( this, this.constructor.defaults );
this.setOptions( options );
// transform
this.translate = new Vector( options.translate );
this.rotate = new Vector( options.rotate );
this.scale = new Vector( onePoint ).multiply( this.scale );
// origin
this.origin = new Vector();
this.renderOrigin = new Vector();
if ( this.addTo ) {
this.addTo.addChild( this );
}
};
Anchor.defaults = {};
Anchor.optionKeys = Object.keys( Anchor.defaults ).concat([
'rotate',
'translate',
'scale',
'addTo',
]);
Anchor.prototype.setOptions = function( options ) {
var optionKeys = this.constructor.optionKeys;
for ( var key in options ) {
if ( optionKeys.indexOf( key ) != -1 ) {
this[ key ] = options[ key ];
}
}
};
Anchor.prototype.addChild = function( shape ) {
if ( this.children.indexOf( shape ) != -1 ) {
return;
}
shape.remove(); // remove previous parent
shape.addTo = this; // keep parent reference
this.children.push( shape );
};
Anchor.prototype.removeChild = function( shape ) {
var index = this.children.indexOf( shape );
if ( index != -1 ) {
this.children.splice( index, 1 );
}
};
Anchor.prototype.remove = function() {
if ( this.addTo ) {
this.addTo.removeChild( this );
}
};
// ----- update ----- //
Anchor.prototype.update = function() {
// update self
this.reset();
// update children
this.children.forEach( function( child ) {
child.update();
});
this.transform( this.translate, this.rotate, this.scale );
};
Anchor.prototype.reset = function() {
this.renderOrigin.set( this.origin );
};
Anchor.prototype.transform = function( translation, rotation, scale ) {
this.renderOrigin.transform( translation, rotation, scale );
// transform children
this.children.forEach( function( child ) {
child.transform( translation, rotation, scale );
});
};
Anchor.prototype.updateGraph = function() {
this.update();
this.updateFlatGraph();
this.flatGraph.forEach( function( item ) {
item.updateSortValue();
});
// z-sort
this.flatGraph.sort( Anchor.shapeSorter );
};
Anchor.shapeSorter = function( a, b ) {
return a.sortValue - b.sortValue;
};
// custom getter to check for flatGraph before using it
Object.defineProperty( Anchor.prototype, 'flatGraph', {
get: function() {
if ( !this._flatGraph ) {
this.updateFlatGraph();
}
return this._flatGraph;
},
set: function( graph ) {
this._flatGraph = graph;
},
});
Anchor.prototype.updateFlatGraph = function() {
this.flatGraph = this.getFlatGraph();
};
// return Array of self & all child graph items
Anchor.prototype.getFlatGraph = function() {
var flatGraph = [ this ];
return this.addChildFlatGraph( flatGraph );
};
Anchor.prototype.addChildFlatGraph = function( flatGraph ) {
this.children.forEach( function( child ) {
var childFlatGraph = child.getFlatGraph();
Array.prototype.push.apply( flatGraph, childFlatGraph );
});
return flatGraph;
};
Anchor.prototype.updateSortValue = function() {
this.sortValue = this.renderOrigin.z;
};
// ----- render ----- //
Anchor.prototype.render = function() {};
// TODO refactor out CanvasRenderer so its not a dependency within anchor.js
Anchor.prototype.renderGraphCanvas = function( ctx ) {
if ( !ctx ) {
throw new Error( 'ctx is ' + ctx + '. ' +
'Canvas context required for render. Check .renderGraphCanvas( ctx ).' );
}
this.flatGraph.forEach( function( item ) {
item.render( ctx, CanvasRenderer );
});
};
Anchor.prototype.renderGraphSvg = function( svg ) {
if ( !svg ) {
throw new Error( 'svg is ' + svg + '. ' +
'SVG required for render. Check .renderGraphSvg( svg ).' );
}
this.flatGraph.forEach( function( item ) {
item.render( svg, SvgRenderer );
});
};
// ----- misc ----- //
Anchor.prototype.copy = function( options ) {
// copy options
var itemOptions = {};
var optionKeys = this.constructor.optionKeys;
optionKeys.forEach( function( key ) {
itemOptions[ key ] = this[ key ];
}, this );
// add set options
utils.extend( itemOptions, options );
var ItemClass = this.constructor;
return new ItemClass( itemOptions );
};
Anchor.prototype.copyGraph = function( options ) {
var clone = this.copy( options );
this.children.forEach( function( child ) {
child.copyGraph({
addTo: clone,
});
});
return clone;
};
Anchor.prototype.normalizeRotate = function() {
this.rotate.x = utils.modulo( this.rotate.x, TAU );
this.rotate.y = utils.modulo( this.rotate.y, TAU );
this.rotate.z = utils.modulo( this.rotate.z, TAU );
};
// ----- subclass ----- //
function getSubclass( Super ) {
return function( defaults ) {
// create constructor
function Item( options ) {
this.create( options || {} );
}
Item.prototype = Object.create( Super.prototype );
Item.prototype.constructor = Item;
Item.defaults = utils.extend( {}, Super.defaults );
utils.extend( Item.defaults, defaults );
// create optionKeys
Item.optionKeys = Super.optionKeys.slice(0);
// add defaults keys to optionKeys, dedupe
Object.keys( Item.defaults ).forEach( function( key ) {
if ( !Item.optionKeys.indexOf( key ) != 1 ) {
Item.optionKeys.push( key );
}
});
Item.subclass = getSubclass( Item );
return Item;
};
}
Anchor.subclass = getSubclass( Anchor );
return Anchor;
}));
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( root );
} else {
// browser global
root.Zdog.Dragger = factory( root );
}
}( this, function factory( window ) {
// quick & dirty drag event stuff
// messes up if multiple pointers/touches
// event support, default to mouse events
var downEvent = 'mousedown';
var moveEvent = 'mousemove';
var upEvent = 'mouseup';
if ( window.PointerEvent ) {
// PointerEvent, Chrome
downEvent = 'pointerdown';
moveEvent = 'pointermove';
upEvent = 'pointerup';
} else if ( 'ontouchstart' in window ) {
// Touch Events, iOS Safari
downEvent = 'touchstart';
moveEvent = 'touchmove';
upEvent = 'touchend';
}
function noop() {}
function Dragger( options ) {
this.create( options || {} );
}
Dragger.prototype.create = function( options ) {
this.onDragStart = options.onDragStart || noop;
this.onDragMove = options.onDragMove || noop;
this.onDragEnd = options.onDragEnd || noop;
this.bindDrag( options.startElement );
};
Dragger.prototype.bindDrag = function( element ) {
element = this.getQueryElement( element );
if ( !element ) {
return;
}
// disable browser gestures #53
element.style.touchAction = 'none';
element.addEventListener( downEvent, this );
};
Dragger.prototype.getQueryElement = function( element ) {
if ( typeof element == 'string' ) {
// with string, query selector
element = document.querySelector( element );
}
return element;
};
Dragger.prototype.handleEvent = function( event ) {
var method = this[ 'on' + event.type ];
if ( method ) {
method.call( this, event );
}
};
Dragger.prototype.onmousedown =
Dragger.prototype.onpointerdown = function( event ) {
this.dragStart( event, event );
};
Dragger.prototype.ontouchstart = function( event ) {
this.dragStart( event, event.changedTouches[0] );
};
Dragger.prototype.dragStart = function( event, pointer ) {
event.preventDefault();
this.dragStartX = pointer.pageX;
this.dragStartY = pointer.pageY;
window.addEventListener( moveEvent, this );
window.addEventListener( upEvent, this );
this.onDragStart( pointer );
};
Dragger.prototype.ontouchmove = function( event ) {
// HACK, moved touch may not be first
this.dragMove( event, event.changedTouches[0] );
};
Dragger.prototype.onmousemove =
Dragger.prototype.onpointermove = function( event ) {
this.dragMove( event, event );
};
Dragger.prototype.dragMove = function( event, pointer ) {
event.preventDefault();
var moveX = pointer.pageX - this.dragStartX;
var moveY = pointer.pageY - this.dragStartY;
this.onDragMove( pointer, moveX, moveY );
};
Dragger.prototype.onmouseup =
Dragger.prototype.onpointerup =
Dragger.prototype.ontouchend =
Dragger.prototype.dragEnd = function() {
window.removeEventListener( moveEvent, this );
window.removeEventListener( upEvent, this );
this.onDragEnd();
};
return Dragger;
}));
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./boilerplate'), require('./anchor'),
require('./dragger') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Illustration = factory( Zdog, Zdog.Anchor, Zdog.Dragger );
}
}( this, function factory( utils, Anchor, Dragger ) {
function noop() {}
var TAU = utils.TAU;
var Illustration = Anchor.subclass({
element: undefined,
centered: true,
zoom: 1,
dragRotate: false,
resize: false,
onPrerender: noop,
onDragStart: noop,
onDragMove: noop,
onDragEnd: noop,
onResize: noop,
});
utils.extend( Illustration.prototype, Dragger.prototype );
Illustration.prototype.create = function( options ) {
Anchor.prototype.create.call( this, options );
Dragger.prototype.create.call( this, options );
this.setElement( this.element );
this.setDragRotate( this.dragRotate );
this.setResize( this.resize );
};
Illustration.prototype.setElement = function( element ) {
element = this.getQueryElement( element );
if ( !element ) {
throw new Error( 'Zdog.Illustration element required. Set to ' + element );
}
var nodeName = element.nodeName.toLowerCase();
if ( nodeName == 'canvas' ) {
this.setCanvas( element );
} else if ( nodeName == 'svg' ) {
this.setSvg( element );
}
};
Illustration.prototype.setSize = function( width, height ) {
width = Math.round( width );
height = Math.round( height );
if ( this.isCanvas ) {
this.setSizeCanvas( width, height );
} else if ( this.isSvg ) {
this.setSizeSvg( width, height );
}
};
Illustration.prototype.setResize = function( resize ) {
this.resize = resize;
// create resize event listener
if ( !this.resizeListener ) {
this.resizeListener = this.onWindowResize.bind( this );
}
// add/remove event listener
if ( resize ) {
window.addEventListener( 'resize', this.resizeListener );
this.onWindowResize();
} else {
window.removeEventListener( 'resize', this.resizeListener );
}
};
// TODO debounce this?
Illustration.prototype.onWindowResize = function() {
this.setMeasuredSize();
this.onResize( this.width, this.height );
};
Illustration.prototype.setMeasuredSize = function() {
var width, height;
var isFullscreen = this.resize == 'fullscreen';
if ( isFullscreen ) {
width = window.innerWidth;
height = window.innerHeight;
} else {
var rect = this.element.getBoundingClientRect();
width = rect.width;
height = rect.height;
}
this.setSize( width, height );
};
// ----- render ----- //
Illustration.prototype.renderGraph = function( item ) {
if ( this.isCanvas ) {
this.renderGraphCanvas( item );
} else if ( this.isSvg ) {
this.renderGraphSvg( item );
}
};
// combo method
Illustration.prototype.updateRenderGraph = function( item ) {
this.updateGraph();
this.renderGraph( item );
};
// ----- canvas ----- //
Illustration.prototype.setCanvas = function( element ) {
this.element = element;
this.isCanvas = true;
// update related properties
this.ctx = this.element.getContext('2d');
// set initial size
this.setSizeCanvas( element.width, element.height );
};
Illustration.prototype.setSizeCanvas = function( width, height ) {
this.width = width;
this.height = height;
// up-rez for hi-DPI devices
var pixelRatio = this.pixelRatio = window.devicePixelRatio || 1;
this.element.width = this.canvasWidth = width * pixelRatio;
this.element.height = this.canvasHeight = height * pixelRatio;
var needsHighPixelRatioSizing = pixelRatio > 1 && !this.resize;
if ( needsHighPixelRatioSizing ) {
this.element.style.width = width + 'px';
this.element.style.height = height + 'px';
}
};
Illustration.prototype.renderGraphCanvas = function( item ) {
item = item || this;
this.prerenderCanvas();
Anchor.prototype.renderGraphCanvas.call( item, this.ctx );
this.postrenderCanvas();
};
Illustration.prototype.prerenderCanvas = function() {
var ctx = this.ctx;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.clearRect( 0, 0, this.canvasWidth, this.canvasHeight );
ctx.save();
if ( this.centered ) {
var centerX = this.width/2 * this.pixelRatio;
var centerY = this.height/2 * this.pixelRatio;
ctx.translate( centerX, centerY );
}
var scale = this.pixelRatio * this.zoom;
ctx.scale( scale, scale );
this.onPrerender( ctx );
};
Illustration.prototype.postrenderCanvas = function() {
this.ctx.restore();
};
// ----- svg ----- //
Illustration.prototype.setSvg = function( element ) {
this.element = element;
this.isSvg = true;
this.pixelRatio = 1;
// set initial size from width & height attributes
var width = element.getAttribute('width');
var height = element.getAttribute('height');
this.setSizeSvg( width, height );
};
Illustration.prototype.setSizeSvg = function( width, height ) {
this.width = width;
this.height = height;
var viewWidth = width / this.zoom;
var viewHeight = height / this.zoom;
var viewX = this.centered ? -viewWidth/2 : 0;
var viewY = this.centered ? -viewHeight/2 : 0;
this.element.setAttribute( 'viewBox', viewX + ' ' + viewY + ' ' +
viewWidth + ' ' + viewHeight );
if ( this.resize ) {
// remove size attributes, let size be determined by viewbox
this.element.removeAttribute('width');
this.element.removeAttribute('height');
} else {
this.element.setAttribute( 'width', width );
this.element.setAttribute( 'height', height );
}
};
Illustration.prototype.renderGraphSvg = function( item ) {
item = item || this;
empty( this.element );
this.onPrerender( this.element );
Anchor.prototype.renderGraphSvg.call( item, this.element );
};
function empty( element ) {
while ( element.firstChild ) {
element.removeChild( element.firstChild );
}
}
// ----- drag ----- //
Illustration.prototype.setDragRotate = function( item ) {
if ( !item ) {
return;
} else if ( item === true ) {
item = this;
}
this.dragRotate = item;
this.bindDrag( this.element );
};
Illustration.prototype.dragStart = function() {
this.dragStartRX = this.dragRotate.rotate.x;
this.dragStartRY = this.dragRotate.rotate.y;
Dragger.prototype.dragStart.apply( this, arguments );
};
Illustration.prototype.dragMove = function( event, pointer ) {
var moveX = pointer.pageX - this.dragStartX;
var moveY = pointer.pageY - this.dragStartY;
var displaySize = Math.min( this.width, this.height );
var moveRY = moveX / displaySize * TAU;
var moveRX = moveY / displaySize * TAU;
this.dragRotate.rotate.x = this.dragStartRX - moveRX;
this.dragRotate.rotate.y = this.dragStartRY - moveRY;
Dragger.prototype.dragMove.apply( this, arguments );
};
return Illustration;
}));
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./vector') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.PathCommand = factory( Zdog.Vector );
}
}( this, function factory( Vector ) {
function PathCommand( method, points, previousPoint ) {
this.method = method;
this.points = points.map( mapVectorPoint );
this.renderPoints = points.map( mapNewVector );
this.previousPoint = previousPoint;
this.endRenderPoint = this.renderPoints[ this.renderPoints.length - 1 ];
// arc actions come with previous point & corner point
// but require bezier control points
if ( method == 'arc' ) {
this.controlPoints = [ new Vector(), new Vector() ];
}
}
function mapVectorPoint( point ) {
if ( point instanceof Vector ) {
return point;
} else {
return new Vector( point );
}
}
function mapNewVector( point ) {
return new Vector( point );
}
PathCommand.prototype.reset = function() {
// reset renderPoints back to orignal points position
var points = this.points;
this.renderPoints.forEach( function( renderPoint, i ) {
var point = points[i];
renderPoint.set( point );
});
};
PathCommand.prototype.transform = function( translation, rotation, scale ) {
this.renderPoints.forEach( function( renderPoint ) {
renderPoint.transform( translation, rotation, scale );
});
};
PathCommand.prototype.render = function( ctx, elem, renderer ) {
return this[ this.method ]( ctx, elem, renderer );
};
PathCommand.prototype.move = function( ctx, elem, renderer ) {
return renderer.move( ctx, elem, this.renderPoints[0] );
};
PathCommand.prototype.line = function( ctx, elem, renderer ) {
return renderer.line( ctx, elem, this.renderPoints[0] );
};
PathCommand.prototype.bezier = function( ctx, elem, renderer ) {
var cp0 = this.renderPoints[0];
var cp1 = this.renderPoints[1];
var end = this.renderPoints[2];
return renderer.bezier( ctx, elem, cp0, cp1, end );
};
var arcHandleLength = 9/16;
PathCommand.prototype.arc = function( ctx, elem, renderer ) {
var prev = this.previousPoint;
var corner = this.renderPoints[0];
var end = this.renderPoints[1];
var cp0 = this.controlPoints[0];
var cp1 = this.controlPoints[1];
cp0.set( prev ).lerp( corner, arcHandleLength );
cp1.set( end ).lerp( corner, arcHandleLength );
return renderer.bezier( ctx, elem, cp0, cp1, end );
};
return PathCommand;
}));
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./boilerplate'), require('./vector'),
require('./path-command'), require('./anchor') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Shape = factory( Zdog, Zdog.Vector, Zdog.PathCommand, Zdog.Anchor );
}
}( this, function factory( utils, Vector, PathCommand, Anchor ) {
var Shape = Anchor.subclass({
stroke: 1,
fill: false,
color: '#333',
closed: true,
visible: true,
path: [ {} ],
front: { z: 1 },
backface: true,
});
Shape.prototype.create = function( options ) {
Anchor.prototype.create.call( this, options );
this.updatePath();
// front
this.front = new Vector( options.front || this.front );
this.renderFront = new Vector( this.front );
this.renderNormal = new Vector();
};
var actionNames = [
'move',
'line',
'bezier',
'arc',
];
Shape.prototype.updatePath = function() {
this.setPath();
this.updatePathCommands();
};
// place holder for Ellipse, Rect, etc.
Shape.prototype.setPath = function() {};
// parse path into PathCommands
Shape.prototype.updatePathCommands = function() {
var previousPoint;
this.pathCommands = this.path.map( function( pathPart, i ) {
// pathPart can be just vector coordinates -> { x, y, z }
// or path instruction -> { arc: [ {x0,y0,z0}, {x1,y1,z1} ] }
var keys = Object.keys( pathPart );
var method = keys[0];
var points = pathPart[ method ];
// default to line if no instruction
var isInstruction = keys.length == 1 && actionNames.indexOf( method ) != -1;
if ( !isInstruction ) {
method = 'line';
points = pathPart;
}
// munge single-point methods like line & move without arrays
var isLineOrMove = method == 'line' || method == 'move';
var isPointsArray = Array.isArray( points );
if ( isLineOrMove && !isPointsArray ) {
points = [ points ];
}
// first action is always move
method = i === 0 ? 'move' : method;
// arcs require previous last point
var command = new PathCommand( method, points, previousPoint );
// update previousLastPoint
previousPoint = command.endRenderPoint;
return command;
});
};
// ----- update ----- //
Shape.prototype.reset = function() {
this.renderOrigin.set( this.origin );
this.renderFront.set( this.front );
// reset command render points
this.pathCommands.forEach( function( command ) {
command.reset();
});
};
Shape.prototype.transform = function( translation, rotation, scale ) {
// calculate render points backface visibility & cone/hemisphere shapes
this.renderOrigin.transform( translation, rotation, scale );
this.renderFront.transform( translation, rotation, scale );
this.renderNormal.set( this.renderOrigin ).subtract( this.renderFront );
// transform points
this.pathCommands.forEach( function( command ) {
command.transform( translation, rotation, scale );
});
// transform children
this.children.forEach( function( child ) {
child.transform( translation, rotation, scale );
});
};
Shape.prototype.updateSortValue = function() {
// sort by average z of all points
// def not geometrically correct, but works for me
var pointCount = this.pathCommands.length;
var firstPoint = this.pathCommands[0].endRenderPoint;
var lastPoint = this.pathCommands[ pointCount - 1 ].endRenderPoint;
// ignore the final point if self closing shape
var isSelfClosing = pointCount > 2 && firstPoint.isSame( lastPoint );
if ( isSelfClosing ) {
pointCount -= 1;
}
var sortValueTotal = 0;
for ( var i = 0; i < pointCount; i++ ) {
sortValueTotal += this.pathCommands[i].endRenderPoint.z;
}
this.sortValue = sortValueTotal / pointCount;
};
// ----- render ----- //
Shape.prototype.render = function( ctx, renderer ) {
var length = this.pathCommands.length;
if ( !this.visible || !length ) {
return;
}
// do not render if hiding backface
this.isFacingBack = this.renderNormal.z > 0;
if ( !this.backface && this.isFacingBack ) {
return;
}
if ( !renderer ) {
throw new Error( 'Zdog renderer required. Set to ' + renderer );
}
// render dot or path
var isDot = length == 1;
if ( renderer.isCanvas && isDot ) {
this.renderCanvasDot( ctx, renderer );
} else {
this.renderPath( ctx, renderer );
}
};
var TAU = utils.TAU;
// Safari does not render lines with no size, have to render circle instead
Shape.prototype.renderCanvasDot = function( ctx ) {
var lineWidth = this.getLineWidth();
if ( !lineWidth ) {
return;
}
ctx.fillStyle = this.getRenderColor();
var point = this.pathCommands[0].endRenderPoint;
ctx.beginPath();
var radius = lineWidth/2;
ctx.arc( point.x, point.y, radius, 0, TAU );
ctx.fill();
};
Shape.prototype.getLineWidth = function() {
if ( !this.stroke ) {
return 0;
}
if ( this.stroke == true ) {
return 1;
}
return this.stroke;
};
Shape.prototype.getRenderColor = function() {
// use backface color if applicable
var isBackfaceColor = typeof this.backface == 'string' && this.isFacingBack;
var color = isBackfaceColor ? this.backface : this.color;
return color;
};
Shape.prototype.renderPath = function( ctx, renderer ) {
var elem = this.getRenderElement( ctx, renderer );
var isTwoPoints = this.pathCommands.length == 2 &&
this.pathCommands[1].method == 'line';
var isClosed = !isTwoPoints && this.closed;
var color = this.getRenderColor();
renderer.renderPath( ctx, elem, this.pathCommands, isClosed );
renderer.stroke( ctx, elem, this.stroke, color, this.getLineWidth() );
renderer.fill( ctx, elem, this.fill, color );
renderer.end( ctx, elem );
};
var svgURI = 'http://www.w3.org/2000/svg';
Shape.prototype.getRenderElement = function( ctx, renderer ) {
if ( !renderer.isSvg ) {
return;
}
if ( !this.svgElement ) {
// create svgElement
this.svgElement = document.createElementNS( svgURI, 'path');
this.svgElement.setAttribute( 'stroke-linecap', 'round' );
this.svgElement.setAttribute( 'stroke-linejoin', 'round' );
}
return this.svgElement;
};
return Shape;
}));
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./anchor') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Group = factory( Zdog.Anchor );
}
}( this, function factory( Anchor ) {
var Group = Anchor.subclass({
updateSort: false,
visible: true,
});
// ----- update ----- //
Group.prototype.updateSortValue = function() {
var sortValueTotal = 0;
this.flatGraph.forEach( function( item ) {
item.updateSortValue();
sortValueTotal += item.sortValue;
});
// average sort value of all points
// def not geometrically correct, but works for me
this.sortValue = sortValueTotal / this.flatGraph.length;
if ( this.updateSort ) {
this.flatGraph.sort( Anchor.shapeSorter );
}
};
// ----- render ----- //
Group.prototype.render = function( ctx, renderer ) {
if ( !this.visible ) {
return;
}
this.flatGraph.forEach( function( item ) {
item.render( ctx, renderer );
});
};
// actual group flatGraph only used inside group
Group.prototype.updateFlatGraph = function() {
// do not include self
var flatGraph = [];
this.flatGraph = this.addChildFlatGraph( flatGraph );
};
// do not include children, group handles rendering & sorting internally
Group.prototype.getFlatGraph = function() {
return [ this ];
};
return Group;
}));
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./shape') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Rect = factory( Zdog.Shape );
}
}( this, function factory( Shape ) {
var Rect = Shape.subclass({
width: 1,
height: 1,
});
Rect.prototype.setPath = function() {
var x = this.width / 2;
var y = this.height / 2;
this.path = [
{ x: -x, y: -y },
{ x: x, y: -y },
{ x: x, y: y },
{ x: -x, y: y },
];
};
return Rect;
}));
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./shape') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.RoundedRect = factory( Zdog.Shape );
}
}( this, function factory( Shape ) {
var RoundedRect = Shape.subclass({
width: 1,
height: 1,
cornerRadius: 0.25,
closed: false,
});
RoundedRect.prototype.setPath = function() {
var xA = this.width / 2;
var yA = this.height / 2;
var shortSide = Math.min( xA, yA );
var cornerRadius = Math.min( this.cornerRadius, shortSide );
var xB = xA - cornerRadius;
var yB = yA - cornerRadius;
var path = [
// top right corner
{ x: xB, y: -yA },
{ arc: [
{ x: xA, y: -yA },
{ x: xA, y: -yB },
]},
];
// bottom right corner
if ( yB ) {
path.push({ x: xA, y: yB });
}
path.push({ arc: [
{ x: xA, y: yA },
{ x: xB, y: yA },
]});
// bottom left corner
if ( xB ) {
path.push({ x: -xB, y: yA });
}
path.push({ arc: [
{ x: -xA, y: yA },
{ x: -xA, y: yB },
]});
// top left corner
if ( yB ) {
path.push({ x: -xA, y: -yB });
}
path.push({ arc: [
{ x: -xA, y: -yA },
{ x: -xB, y: -yA },
]});
// back to top right corner
if ( xB ) {
path.push({ x: xB, y: -yA });
}
this.path = path;
};
return RoundedRect;
}));
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./shape') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Ellipse = factory( Zdog.Shape );
}
}( this, function factory( Shape ) {
var Ellipse = Shape.subclass({
diameter: 1,
width: undefined,
height: undefined,
quarters: 4,
closed: false,
});
Ellipse.prototype.setPath = function() {
var width = this.width != undefined ? this.width : this.diameter;
var height = this.height != undefined ? this.height : this.diameter;
var x = width / 2;
var y = height / 2;
this.path = [
{ x: 0, y: -y },
{ arc: [ // top right
{ x: x, y: -y },
{ x: x, y: 0 },
]},
];
// bottom right
if ( this.quarters > 1 ) {
this.path.push({ arc: [
{ x: x, y: y },
{ x: 0, y: y },
]});
}
// bottom left
if ( this.quarters > 2 ) {
this.path.push({ arc: [
{ x: -x, y: y },
{ x: -x, y: 0 },
]});
}
// top left
if ( this.quarters > 3 ) {
this.path.push({ arc: [
{ x: -x, y: -y },
{ x: 0, y: -y },
]});
}
};
return Ellipse;
}));
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./boilerplate'), require('./shape') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Polygon = factory( Zdog, Zdog.Shape );
}
}( this, function factory( utils, Shape ) {
var Polygon = Shape.subclass({
sides: 3,
radius: 0.5,
});
var TAU = utils.TAU;
Polygon.prototype.setPath = function() {
this.path = [];
for ( var i=0; i < this.sides; i++ ) {
var theta = i/this.sides * TAU - TAU/4;
var x = Math.cos( theta ) * this.radius;
var y = Math.sin( theta ) * this.radius;
this.path.push({ x: x, y: y });
}
};
return Polygon;
}));
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./boilerplate'), require('./vector'),
require('./anchor'), require('./ellipse') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Hemisphere = factory( Zdog, Zdog.Vector, Zdog.Anchor, Zdog.Ellipse );
}
}( this, function factory( utils, Vector, Anchor, Ellipse ) {
var Hemisphere = Ellipse.subclass({
fill: true,
});
var TAU = utils.TAU;
Hemisphere.prototype.create = function() {
// call super
Ellipse.prototype.create.apply( this, arguments );
// composite shape, create child shapes
this.apex = new Anchor({
addTo: this,
translate: { z: this.diameter/2 },
});
// vector used for calculation
this.renderCentroid = new Vector();
};
Hemisphere.prototype.updateSortValue = function() {
// centroid of hemisphere is 3/8 between origin and apex
this.renderCentroid.set( this.renderOrigin )
.lerp( this.apex.renderOrigin, 3/8 );
this.sortValue = this.renderCentroid.z;
};
Hemisphere.prototype.render = function( ctx, renderer ) {
this.renderDome( ctx, renderer );
// call super
Ellipse.prototype.render.apply( this, arguments );
};
Hemisphere.prototype.renderDome = function( ctx, renderer ) {
if ( !this.visible ) {
return;
}
var elem = this.getDomeRenderElement( ctx, renderer );
var contourAngle = Math.atan2( this.renderNormal.y, this.renderNormal.x );
var domeRadius = this.diameter/2 * this.renderNormal.magnitude();
var x = this.renderOrigin.x;
var y = this.renderOrigin.y;
if ( renderer.isCanvas ) {
// canvas
var startAngle = contourAngle + TAU/4;
var endAngle = contourAngle - TAU/4;
ctx.beginPath();
ctx.arc( x, y, domeRadius, startAngle, endAngle );
} else if ( renderer.isSvg ) {
// svg
contourAngle = (contourAngle - TAU/4) / TAU * 360;
this.domeSvgElement.setAttribute( 'd', 'M ' + -domeRadius + ',0 A ' +
domeRadius + ',' + domeRadius + ' 0 0 1 ' + domeRadius + ',0' );
this.domeSvgElement.setAttribute( 'transform',
'translate(' + x + ',' + y + ' ) rotate(' + contourAngle + ')' );
}
renderer.stroke( ctx, elem, this.stroke, this.color, this.getLineWidth() );
renderer.fill( ctx, elem, this.fill, this.color );
renderer.end( ctx, elem );
};
var svgURI = 'http://www.w3.org/2000/svg';
Hemisphere.prototype.getDomeRenderElement = function( ctx, renderer ) {
if ( !renderer.isSvg ) {
return;
}
if ( !this.domeSvgElement ) {
// create svgElement
this.domeSvgElement = document.createElementNS( svgURI, 'path');
this.domeSvgElement.setAttribute( 'stroke-linecap', 'round' );
this.domeSvgElement.setAttribute( 'stroke-linejoin', 'round' );
}
return this.domeSvgElement;
};
return Hemisphere;
}));
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./boilerplate'),
require('./path-command'), require('./shape'), require('./group'),
require('./ellipse') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Cylinder = factory( Zdog, Zdog.PathCommand, Zdog.Shape,
Zdog.Group, Zdog.Ellipse );
}
}( this, function factory( utils, PathCommand, Shape, Group, Ellipse ) {
function noop() {}
// ----- CylinderGroup ----- //
var CylinderGroup = Group.subclass({
color: '#333',
updateSort: true,
});
CylinderGroup.prototype.create = function() {
Group.prototype.create.apply( this, arguments );
this.pathCommands = [
new PathCommand( 'move', [ {} ] ),
new PathCommand( 'line', [ {} ] ),
];
};
CylinderGroup.prototype.render = function( ctx, renderer ) {
this.renderCylinderSurface( ctx, renderer );
Group.prototype.render.apply( this, arguments );
};
CylinderGroup.prototype.renderCylinderSurface = function( ctx, renderer ) {
if ( !this.visible ) {
return;
}
// render cylinder surface
var elem = this.getRenderElement( ctx, renderer );
var frontBase = this.frontBase;
var rearBase = this.rearBase;
var scale = frontBase.renderNormal.magnitude();
var strokeWidth = frontBase.diameter * scale + frontBase.getLineWidth();
// set path command render points
this.pathCommands[0].renderPoints[0].set( frontBase.renderOrigin );
this.pathCommands[1].renderPoints[0].set( rearBase.renderOrigin );
if ( renderer.isCanvas ) {
ctx.lineCap = 'butt'; // nice
}
renderer.renderPath( ctx, elem, this.pathCommands );
renderer.stroke( ctx, elem, true, this.color, strokeWidth );
renderer.end( ctx, elem );
if ( renderer.isCanvas ) {
ctx.lineCap = 'round'; // reset
}
};
var svgURI = 'http://www.w3.org/2000/svg';
CylinderGroup.prototype.getRenderElement = function( ctx, renderer ) {
if ( !renderer.isSvg ) {
return;
}
if ( !this.svgElement ) {
// create svgElement
this.svgElement = document.createElementNS( svgURI, 'path');
}
return this.svgElement;
};
// prevent double-creation in parent.copyGraph()
// only create in Cylinder.create()
CylinderGroup.prototype.copyGraph = noop;
// ----- CylinderEllipse ----- //
var CylinderEllipse = Ellipse.subclass();
CylinderEllipse.prototype.copyGraph = noop;
// ----- Cylinder ----- //
var Cylinder = Shape.subclass({
diameter: 1,
length: 1,
frontFace: undefined,
fill: true,
});
var TAU = utils.TAU;
Cylinder.prototype.create = function() {
// call super
Shape.prototype.create.apply( this, arguments );
// composite shape, create child shapes
// CylinderGroup to render cylinder surface then bases
this.group = new CylinderGroup({
addTo: this,
color: this.color,
visible: this.visible,
});
var baseZ = this.length/2;
var baseColor = this.backface || true;
// front outside base
this.frontBase = this.group.frontBase = new Ellipse({
addTo: this.group,
diameter: this.diameter,
translate: { z: baseZ },
rotate: { y: TAU/2 },
color: this.color,
stroke: this.stroke,
fill: this.fill,
backface: this.frontFace || baseColor,
visible: this.visible,
});
// back outside base
this.rearBase = this.group.rearBase = this.frontBase.copy({
translate: { z: -baseZ },
rotate: { y: 0 },
backface: baseColor,
});
};
// Cylinder shape does not render anything
Cylinder.prototype.render = function() {};
// ----- set child properties ----- //
var childProperties = [ 'stroke', 'fill', 'color', 'visible' ];
childProperties.forEach( function( property ) {
// use proxy property for custom getter & setter
var _prop = '_' + property;
Object.defineProperty( Cylinder.prototype, property, {
get: function() {
return this[ _prop ];
},
set: function( value ) {
this[ _prop ] = value;
// set property on children
if ( this.frontBase ) {
this.frontBase[ property ] = value;
this.rearBase[ property ] = value;
this.group[ property ] = value;
}
},
});
});
// TODO child property setter for backface, frontBaseColor, & rearBaseColor
return Cylinder;
}));
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./boilerplate'), require('./vector'),
require('./path-command'), require('./anchor'), require('./ellipse') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Cone = factory( Zdog, Zdog.Vector, Zdog.PathCommand,
Zdog.Anchor, Zdog.Ellipse );
}
}( this, function factory( utils, Vector, PathCommand, Anchor, Ellipse ) {
var Cone = Ellipse.subclass({
length: 1,
fill: true,
});
var TAU = utils.TAU;
Cone.prototype.create = function() {
// call super
Ellipse.prototype.create.apply( this, arguments );
// composite shape, create child shapes
this.apex = new Anchor({
addTo: this,
translate: { z: this.length },
});
// vectors used for calculation
this.renderApex = new Vector();
this.renderCentroid = new Vector();
this.tangentA = new Vector();
this.tangentB = new Vector();
this.surfacePathCommands = [
new PathCommand( 'move', [ {} ] ), // points set in renderConeSurface
new PathCommand( 'line', [ {} ] ),
new PathCommand( 'line', [ {} ] ),
];
};
Cone.prototype.updateSortValue = function() {
// center of cone is one third of its length
this.renderCentroid.set( this.renderOrigin )
.lerp( this.apex.renderOrigin, 1/3 );
this.sortValue = this.renderCentroid.z;
};
Cone.prototype.render = function( ctx, renderer ) {
this.renderConeSurface( ctx, renderer );
Ellipse.prototype.render.apply( this, arguments );
};
Cone.prototype.renderConeSurface = function( ctx, renderer ) {
if ( !this.visible ) {
return;
}
this.renderApex.set( this.apex.renderOrigin )
.subtract( this.renderOrigin );
var scale = this.renderNormal.magnitude();
var apexDistance = this.renderApex.magnitude2d();
var normalDistance = this.renderNormal.magnitude2d();
// eccentricity
var eccenAngle = Math.acos( normalDistance / scale );
var eccen = Math.sin( eccenAngle );
var radius = this.diameter/2 * scale;
// does apex extend beyond eclipse of face
var isApexVisible = radius * eccen < apexDistance;
if ( !isApexVisible ) {
return;
}
// update tangents
var apexAngle = Math.atan2( this.renderNormal.y, this.renderNormal.x ) +
TAU/2;
var projectLength = apexDistance / eccen;
var projectAngle = Math.acos( radius / projectLength );
// set tangent points
var tangentA = this.tangentA;
var tangentB = this.tangentB;
tangentA.x = Math.cos( projectAngle ) * radius * eccen;
tangentA.y = Math.sin( projectAngle ) * radius;
tangentB.set( this.tangentA );
tangentB.y *= -1;
tangentA.rotateZ( apexAngle );
tangentB.rotateZ( apexAngle );
tangentA.add( this.renderOrigin );
tangentB.add( this.renderOrigin );
this.setSurfaceRenderPoint( 0, tangentA );
this.setSurfaceRenderPoint( 1, this.apex.renderOrigin );
this.setSurfaceRenderPoint( 2, tangentB );
// render
var elem = this.getSurfaceRenderElement( ctx, renderer );
renderer.renderPath( ctx, elem, this.surfacePathCommands );
renderer.stroke( ctx, elem, this.stroke, this.color, this.getLineWidth() );
renderer.fill( ctx, elem, this.fill, this.color );
renderer.end( ctx, elem );
};
var svgURI = 'http://www.w3.org/2000/svg';
Cone.prototype.getSurfaceRenderElement = function( ctx, renderer ) {
if ( !renderer.isSvg ) {
return;
}
if ( !this.surfaceSvgElement ) {
// create svgElement
this.surfaceSvgElement = document.createElementNS( svgURI, 'path');
this.surfaceSvgElement.setAttribute( 'stroke-linecap', 'round' );
this.surfaceSvgElement.setAttribute( 'stroke-linejoin', 'round' );
}
return this.surfaceSvgElement;
};
Cone.prototype.setSurfaceRenderPoint = function( index, point ) {
var renderPoint = this.surfacePathCommands[ index ].renderPoints[0];
renderPoint.set( point );
};
return Cone;
}));
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./boilerplate'), require('./anchor'),
require('./shape'), require('./rect') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Box = factory( Zdog, Zdog.Anchor, Zdog.Shape, Zdog.Rect );
}
}( this, function factory( utils, Anchor, Shape, Rect ) {
// ----- BoxRect ----- //
var BoxRect = Rect.subclass();
// prevent double-creation in parent.copyGraph()
// only create in Box.create()
BoxRect.prototype.copyGraph = function() {};
// ----- Box ----- //
var TAU = utils.TAU;
var faceNames = [
'frontFace',
'rearFace',
'leftFace',
'rightFace',
'topFace',
'bottomFace',
];
var boxDefaults = utils.extend( {}, Shape.defaults );
delete boxDefaults.path;
faceNames.forEach( function( faceName ) {
boxDefaults[ faceName ] = true;
});
utils.extend( boxDefaults, {
width: 1,
height: 1,
depth: 1,
fill: true,
});
var Box = Anchor.subclass( boxDefaults );
Box.prototype.create = function( options ) {
Anchor.prototype.create.call( this, options );
this.updatePath();
// HACK reset fill to trigger face setter
this.fill = this.fill;
};
Box.prototype.updatePath = function() {
// reset all faces to trigger setters
faceNames.forEach( function( faceName ) {
this[ faceName ] = this[ faceName ];
}, this );
};
faceNames.forEach( function( faceName ) {
var _faceName = '_' + faceName;
Object.defineProperty( Box.prototype, faceName, {
get: function() {
return this[ _faceName ];
},
set: function( value ) {
this[ _faceName ] = value;
this.setFace( faceName, value );
},
});
});
Box.prototype.setFace = function( faceName, value ) {
var rectProperty = faceName + 'Rect';
var rect = this[ rectProperty ];
// remove if false
if ( !value ) {
this.removeChild( rect );
return;
}
// update & add face
var options = this.getFaceOptions( faceName );
options.color = typeof value == 'string' ? value : this.color;
if ( rect ) {
// update previous
rect.setOptions( options );
} else {
// create new
rect = this[ rectProperty ] = new BoxRect( options );
}
rect.updatePath();
this.addChild( rect );
};
Box.prototype.getFaceOptions = function( faceName ) {
return {
frontFace: {
width: this.width,
height: this.height,
translate: { z: this.depth/2 },
},
rearFace: {
width: this.width,
height: this.height,
translate: { z: -this.depth/2 },
rotate: { y: TAU/2 },
},
leftFace: {
width: this.depth,
height: this.height,
translate: { x: -this.width/2 },
rotate: { y: -TAU/4 },
},
rightFace: {
width: this.depth,
height: this.height,
translate: { x: this.width/2 },
rotate: { y: TAU/4 },
},
topFace: {
width: this.width,
height: this.depth,
translate: { y: -this.height/2 },
rotate: { x: -TAU/4 },
},
bottomFace: {
width: this.width,
height: this.depth,
translate: { y: this.height/2 },
rotate: { x: TAU/4 },
},
}[ faceName ];
};
// ----- set face properties ----- //
var childProperties = [ 'color', 'stroke', 'fill', 'backface', 'front',
'visible' ];
childProperties.forEach( function( property ) {
// use proxy property for custom getter & setter
var _prop = '_' + property;
Object.defineProperty( Box.prototype, property, {
get: function() {
return this[ _prop ];
},
set: function( value ) {
this[ _prop ] = value;
faceNames.forEach( function( faceName ) {
var rect = this[ faceName + 'Rect' ];
var isFaceColor = typeof this[ faceName ] == 'string';
var isColorUnderwrite = property == 'color' && isFaceColor;
if ( rect && !isColorUnderwrite ) {
rect[ property ] = value;
}
}, this );
},
});
});
return Box;
}));
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory(
require('./boilerplate'),
require('./canvas-renderer'),
require('./svg-renderer'),
require('./vector'),
require('./anchor'),
require('./dragger'),
require('./illustration'),
require('./path-command'),
require('./shape'),
require('./group'),
require('./rect'),
require('./rounded-rect'),
require('./ellipse'),
require('./polygon'),
require('./hemisphere'),
require('./cylinder'),
require('./cone'),
require('./box')
);
} else if ( typeof define == 'function' && define.amd ) {
// AMD
define( 'zdog', [], root.Zdog );
}
})( this, function factory( Zdog, CanvasRenderer, SvgRenderer, Vector, Anchor,
Dragger, Illustration, PathCommand, Shape, Group, Rect, RoundedRect,
Ellipse, Polygon, Hemisphere, Cylinder, Cone, Box ) {
Zdog.CanvasRenderer = CanvasRenderer;
Zdog.SvgRenderer = SvgRenderer;
Zdog.Vector = Vector;
Zdog.Anchor = Anchor;
Zdog.Dragger = Dragger;
Zdog.Illustration = Illustration;
Zdog.PathCommand = PathCommand;
Zdog.Shape = Shape;
Zdog.Group = Group;
Zdog.Rect = Rect;
Zdog.RoundedRect = RoundedRect;
Zdog.Ellipse = Ellipse;
Zdog.Polygon = Polygon;
Zdog.Hemisphere = Hemisphere;
Zdog.Cylinder = Cylinder;
Zdog.Cone = Cone;
Zdog.Box = Box;
return Zdog;
});
const radiansToDegrees = (_val) => {
return _val * (Math.PI/180);
}
const componentToHex = (c) => {
var hex = c.toString(16);
return hex.length == 1 ? "0" + hex : hex;
}
const rgbToHex = (r, g, b) => {
return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
}
const hslToRgb = (_h, s, l) => {
var h = Math.min(_h, 359)/60;
var c = (1-Math.abs((2*l)-1))*s;
var x = c*(1-Math.abs((h % 2)-1));
var m = l - (0.5*c);
var r = m, g = m, b = m;
if (h < 1) {
r += c, g = +x, b += 0;
} else if (h < 2) {
r += x, g += c, b += 0;
} else if (h < 3) {
r += 0, g += c, b += x;
} else if (h < 4) {
r += 0, g += x, b += c;
} else if (h < 5) {
r += x, g += 0, b += c;
} else if (h < 6) {
r += c, g += 0, b += x;
} else {
r = 0, g = 0, b = 0;
}
//return 'rgb(' + Math.floor(r*255) + ', ' + Math.floor(g*255) + ', ' + Math.floor(b*255) + ')';
return rgbToHex(Math.floor(r*255), Math.floor(g*255) , Math.floor(b*255) );
}
const createSpectrum = (length) => {
var colors = [];
// 270 if we don't want the spectrum to loop
// 360 if we do want the spectrum to loop
var step = 360/length;
for (var i = 1; i <= length; i++) {
var color = hslToRgb((i)*step, 0.5, 0.5);
colors.push(color);
}
return colors;
}
//48 because we have 4 faces to populate x 12 cubes
const spectrum = createSpectrum(48).reverse();
let illo = new Zdog.Illustration({
element: '.zdog-canvas',
dragRotate: true
});
var mainGroup = new Zdog.Anchor({
addTo: illo,
translate: {x: -0, y: 0, z: 0 }
});
var boxGroup = new Zdog.Group({
addTo: mainGroup,
translate: {x: -400, y: 0, z: 0 }
});
const makeBoxes = () => {
let leftFaceCount = 0, topFaceCount = 11, rightFaceCount = 23, bottomFaceCount = 35;
for(let i = 0; i < 12; i++){
let boxAnchor = new Zdog.Anchor({
addTo: mainGroup,
rotate: { y: radiansToDegrees(360 - (i * 30))},
translate: {x: 0, y: 0, z: 0 }
});
let box = new Zdog.Box({
addTo: boxAnchor,
translate: {x: -200},
width: 60,
height: 60,
depth: 60,
stroke: 2,
color: '#000',
// remove left & right faces
leftFace: spectrum[leftFaceCount],
rightFace: spectrum[rightFaceCount],
rearFace: '#000',
topFace: spectrum[topFaceCount],
bottomFace: spectrum[bottomFaceCount],
backface: true,
rotate: { z: radiansToDegrees((90/12) * i)}
})
leftFaceCount ++;
rightFaceCount ++;
topFaceCount ++;
bottomFaceCount ++;
}
}
mainGroup.rotate.x = radiansToDegrees(-45);
function render () {
for(let i = 1; i < mainGroup.children.length; i++) {
mainGroup.children[i].children[0].rotate.z -= (0.031)
}
illo.updateRenderGraph();
requestAnimationFrame(render)
}
makeBoxes();
render();
canvas烟花效果
效果图
完整代码
HTML
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="UTF-8">
<title>HTML5 Canvas烟花绽放庆祝活动场景DEMO演示</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div style="text-align:center;clear:both;">
<script src="/gg_bd_ad_720x90.js" type="text/javascript"></script>
<script src="/follow.js" type="text/javascript"></script>
</div>
<canvas></canvas>
<canvas></canvas>
<canvas></canvas>
<script src="js/index.js"></script>
</body>
</html>
CSS
body {
margin: 0;
background: black;
}
canvas {
position: absolute;
}
JavaScript
// CLASSES
class Shard {
constructor(x, y, hue) {
this.x = x;
this.y = y;
this.hue = hue;
this.lightness = 50;
this.size = 15 + Math.random() * 10;
const angle = Math.random() * 2 * Math.PI;
const blastSpeed = 1 + Math.random() * 6;
this.xSpeed = Math.cos(angle) * blastSpeed;
this.ySpeed = Math.sin(angle) * blastSpeed;
this.target = getTarget();
this.ttl = 100;
this.timer = 0;
}
draw() {
ctx2.fillStyle = `hsl(${this.hue}, 100%, ${this.lightness}%)`;
ctx2.beginPath();
ctx2.arc(this.x, this.y, this.size, 0, 2 * Math.PI);
ctx2.closePath();
ctx2.fill();
}
update() {
if (this.target) {
const dx = this.target.x - this.x;
const dy = this.target.y - this.y;
const dist = Math.sqrt(dx * dx + dy * dy);
const a = Math.atan2(dy, dx);
const tx = Math.cos(a) * 5;
const ty = Math.sin(a) * 5;
this.size = lerp(this.size, 1.5, 0.05);
if (dist < 5) {
this.lightness = lerp(this.lightness, 100, 0.01);
this.xSpeed = this.ySpeed = 0;
this.x = lerp(this.x, this.target.x + fidelity / 2, 0.05);
this.y = lerp(this.y, this.target.y + fidelity / 2, 0.05);
this.timer += 1;
} else
if (dist < 10) {
this.lightness = lerp(this.lightness, 100, 0.01);
this.xSpeed = lerp(this.xSpeed, tx, 0.1);
this.ySpeed = lerp(this.ySpeed, ty, 0.1);
this.timer += 1;
} else
{
this.xSpeed = lerp(this.xSpeed, tx, 0.02);
this.ySpeed = lerp(this.ySpeed, ty, 0.02);
}
} else
{
this.ySpeed += 0.05;
//this.xSpeed = lerp(this.xSpeed, 0, 0.1);
this.size = lerp(this.size, 1, 0.05);
if (this.y > c2.height) {
shards.forEach((shard, idx) => {
if (shard === this) {
shards.splice(idx, 1);
}
});
}
}
this.x = this.x + this.xSpeed;
this.y = this.y + this.ySpeed;
}}
class Rocket {
constructor() {
const quarterW = c2.width / 4;
this.x = quarterW + Math.random() * (c2.width - quarterW);
this.y = c2.height - 15;
this.angle = Math.random() * Math.PI / 4 - Math.PI / 6;
this.blastSpeed = 6 + Math.random() * 7;
this.shardCount = 15 + Math.floor(Math.random() * 15);
this.xSpeed = Math.sin(this.angle) * this.blastSpeed;
this.ySpeed = -Math.cos(this.angle) * this.blastSpeed;
this.hue = Math.floor(Math.random() * 360);
this.trail = [];
}
draw() {
ctx2.save();
ctx2.translate(this.x, this.y);
ctx2.rotate(Math.atan2(this.ySpeed, this.xSpeed) + Math.PI / 2);
ctx2.fillStyle = `hsl(${this.hue}, 100%, 50%)`;
ctx2.fillRect(0, 0, 5, 15);
ctx2.restore();
}
update() {
this.x = this.x + this.xSpeed;
this.y = this.y + this.ySpeed;
this.ySpeed += 0.1;
}
explode() {
for (let i = 0; i < 70; i++) {
shards.push(new Shard(this.x, this.y, this.hue));
}
}}
// INITIALIZATION
const [c1, c2, c3] = document.querySelectorAll('canvas');
const [ctx1, ctx2, ctx3] = [c1, c2, c3].map(c => c.getContext('2d'));
let fontSize = 200;
const rockets = [];
const shards = [];
const targets = [];
const fidelity = 3;
let counter = 0;
c2.width = c3.width = window.innerWidth;
c2.height = c3.height = window.innerHeight;
ctx1.fillStyle = '#000';
const text = 'HTML5';
let textWidth = 99999999;
while (textWidth > window.innerWidth) {
ctx1.font = `900 ${fontSize--}px Arial`;
textWidth = ctx1.measureText(text).width;
}
c1.width = textWidth;
c1.height = fontSize * 1.5;
ctx1.font = `900 ${fontSize}px Arial`;
ctx1.fillText(text, 0, fontSize);
const imgData = ctx1.getImageData(0, 0, c1.width, c1.height);
for (let i = 0, max = imgData.data.length; i < max; i += 4) {
const alpha = imgData.data[i + 3];
const x = Math.floor(i / 4) % imgData.width;
const y = Math.floor(i / 4 / imgData.width);
if (alpha && x % fidelity === 0 && y % fidelity === 0) {
targets.push({ x, y });
}
}
ctx3.fillStyle = '#FFF';
ctx3.shadowColor = '#FFF';
ctx3.shadowBlur = 25;
// ANIMATION LOOP
(function loop() {
ctx2.fillStyle = "rgba(0, 0, 0, .1)";
ctx2.fillRect(0, 0, c2.width, c2.height);
//ctx2.clearRect(0, 0, c2.width, c2.height);
counter += 1;
if (counter % 15 === 0) {
rockets.push(new Rocket());
}
rockets.forEach((r, i) => {
r.draw();
r.update();
if (r.ySpeed > 0) {
r.explode();
rockets.splice(i, 1);
}
});
shards.forEach((s, i) => {
s.draw();
s.update();
if (s.timer >= s.ttl || s.lightness >= 99) {
ctx3.fillRect(s.target.x, s.target.y, fidelity + 1, fidelity + 1);
shards.splice(i, 1);
}
});
requestAnimationFrame(loop);
})();
// HELPER FUNCTIONS
const lerp = (a, b, t) => Math.abs(b - a) > 0.1 ? a + t * (b - a) : b;
function getTarget() {
if (targets.length > 0) {
const idx = Math.floor(Math.random() * targets.length);
let { x, y } = targets[idx];
targets.splice(idx, 1);
x += c2.width / 2 - textWidth / 2;
y += c2.height / 2 - fontSize / 2;
return { x, y };
}
}
canvas制作马赛克&飞鸟动画&小球拖拽动画
canvas制作马赛克&飞鸟动画&小球拖拽动画
canvas实现移动端刮刮卡
canvas实现移动端刮刮卡
带你使用canvas实现时钟
带你使用canvas实现时钟
后记
喜欢的小伙伴记得关注哦
更多好玩的特效等你来拿哦