25-августа-2023, 21:57 25 0
Красивый анимированный крипто котёнок который выполнен при помощи HTML, CSS и CANVAS. Котёнок находится в прозрачной призме, которая в свою очередь может вращаться в разные стороны, а так же реагирует на движение курсора. Котёнка можно использовать для украшения сайта или тематического ресурса. И так перейдём к установке.
HTML
<canvas></canvas>CSS
html { height: 100%; }
body {
font-family: sans-serif;
color: white;
min-height: 100%;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
background: #013;
font-family: sans-serif;
text-align: center;
}
canvas {
display: block;
margin: 0px auto 20px;
cursor: move;
}JS
Скрипт инициализации
var illoElem = document.querySelector('canvas');
var illoSize = 64;
var minWindowSize = Math.min( window.innerWidth - 20, window.innerHeight - 80 );
var zoom = Math.floor( minWindowSize / illoSize );
illoElem.setAttribute( 'width', illoSize * zoom );
illoElem.setAttribute( 'height', illoSize * zoom );
var isSpinning = true;
var TAU = Zdog.TAU;
var illo = new Zdog.Illustration({
element: illoElem,
zoom: zoom,
rotate: { x: -TAU/32 },
dragRotate: true,
ondragStart: function() {
isSpinning = false;
},
});
// colors
var magenta = '#F49';
var midnight = '#103';
var white = 'white';
// -- illustration shapes --- //
var cat = new Zdog.Group({
addTo: illo,
updateSort: true,
});
// body
new Zdog.Shape({
path: [ { y: -1 }, { y: 1} ],
scale: { y: 3 },
addTo: cat,
stroke: 14,
color: magenta,
});
var face = new Zdog.Anchor({
addTo: cat,
translate: { y: -4, z: 6.5 },
});
// nose
new Zdog.Shape({
path: [
{ x: -1 },
{ x: 1 },
{ y: 1 },
],
scale: { x: 0.25, y: 0.25 },
addTo: face,
translate: { z: 1.5 },
stroke: 1,
color: midnight,
});
// tummy
new Zdog.RoundedRect({
width: 5,
height: 7,
cornerRadius: 2.5,
addTo: cat,
translate: { y: 3.5, z: 5 },
// rotate: { x: TAU/64 },
color: white,
stroke: 3,
fill: true,
});
// chin
new Zdog.Shape({
path: [ { x: -1 }, { x: 1 } ],
scale: { x: 2 },
addTo: cat,
translate: { y: -3, z: 4 },
stroke: 4,
color: magenta,
});
// tail
new Zdog.Shape({
path: [ { y: 0 }, { y: 8 } ],
addTo: cat,
translate: { y: 7, z: -4 },
rotate: { x: -TAU/32 },
stroke: 1,
color: magenta,
});
var backLine = new Zdog.Shape({
path: [ { x: -1 }, { x: 1 } ],
scale: { x: 3 },
addTo: cat,
translate: { y: 0, z: -6.5 },
stroke: 0.5,
color: '#F7A',
});
backLine.copy({
translate: { y: -3, z: -6.5 },
});
backLine.copy({
translate: { y: 3, z: -6.5 },
});
[ -1, 1 ].forEach( function( xSide ) {
// eye
new Zdog.Shape({
path: [ { y: -1 }, { y: 1} ],
scale: { y: 0.3 },
addTo: face,
translate: { x: 0.75*xSide, y: -1.5 },
stroke: 0.8,
color: midnight,
});
// maw
new Zdog.Shape({
path: [ { x: -1 }, { x: 1} ],
scale: { x: 0.4 },
addTo: face,
translate: { x: 1*xSide, y: 0.5, z: 0.5 },
stroke: 1.5,
color: white,
});
// whisker
var whisker = new Zdog.Shape({
path: [
{ x: 0, y: 0 },
{ x: 1, y: 1 },
],
scale: { x: xSide*3, y: 0.75 },
addTo: face,
translate: { x: 2.5*xSide, y: 0.5 },
color: white,
stroke: 0.25,
});
whisker.copy({
scale: { x: xSide*3, y: -0.75 },
});
// ear
new Zdog.Shape({
path: [
{ x: 0, y: 0 },
{ x: 1, y: 1 },
{ x: 1, y: -1 },
],
scale: { x: 2*xSide, y: 1.5 },
addTo: cat,
translate: { x: 2*xSide, y: -8 },
color: magenta,
stroke: 3,
fill: true,
});
// arm
var arm = new Zdog.Shape({
path: [ { y: 0 }, { y: 3.5 } ],
addTo: cat,
translate: { x: 3.5*xSide, y: -1, z: 5.5 },
rotate: { x: TAU/16 },
stroke: 3,
color: magenta,
});
// leg
arm.copy({
translate: { x: 3.5*xSide, y: 8, z: 2 },
rotate: {},
});
});
var diamondPanel = new Zdog.Shape({
path: [
{ x: 0, y: 1, z: -0 },
{ x: -1, y: 0, z: 1 },
{ x: 1, y: 0, z: 1 },
],
scale: { x: 12, y: 30, z: -12 },
addTo: illo,
stroke: false,
fill: true,
color: 'hsla(60, 100%, 50%, 0.1)',
});
diamondPanel.copy({
rotate: { y: TAU/4*1 },
color: 'hsla(60, 100%, 50%, 0.2)',
});
diamondPanel.copy({
rotate: { y: TAU/4*2 },
color: 'hsla(60, 100%, 50%, 0.3)',
});
diamondPanel.copy({
rotate: { y: TAU/4*3 },
color: 'hsla(60, 100%, 50%, 0.4)',
});
diamondPanel.copy({
scale: { x: 12, y: -30, z: -12 },
rotate: { y: TAU/4*0 },
color: 'hsla(60, 100%, 50%, 0.4)',
});
diamondPanel.copy({
scale: { x: 12, y: -30, z: -12 },
rotate: { y: TAU/4*1 },
color: 'hsla(60, 100%, 50%, 0.3)',
});
diamondPanel.copy({
scale: { x: 12, y: -30, z: -12 },
rotate: { y: TAU/4*2 },
color: 'hsla(60, 100%, 50%, 0.2)',
});
diamondPanel.copy({
scale: { x: 12, y: -30, z: -12 },
rotate: { y: TAU/4*3 },
color: 'hsla(60, 100%, 50%, 0.1)',
});
// ----- animate ----- //
function animate() {
illo.rotate.y += isSpinning ? -TAU/150 : 0;
illo.updateRenderGraph();
requestAnimationFrame( animate );
}
animate();Jаvascript
Создаем отдельный файл скрипта с произвольным названием копируем в него ниже приведённое содержимое и подключаем его перед тегом body
/*!
* Zdog v1.1.3
* Round, flat, designer-friendly pseudo-3D engine
* Licensed MIT
* https://zzz.dog
* Copyright 2020 Metafizzy
*/
/**
* Boilerplate & utils
*/
( 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;
} ) );
/**
* CanvasRenderer
*/
( 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;
} ) );
/**
* SvgRenderer
*/
( 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( /* elem */) {
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;
} ) );
/**
* Vector
*/
( 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;
} ) );
/**
* Anchor
*/
( 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;
} ) );
/**
* Dragger
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory();
} else {
// browser global
root.Zdog.Dragger = factory();
}
}( this, function factory() {
// quick & dirty drag event stuff
// messes up if multiple pointers/touches
// check for browser window #85
var hasWindow = typeof window != 'undefined';
// event support, default to mouse events
var downEvent = 'mousedown';
var moveEvent = 'mousemove';
var upEvent = 'mouseup';
if ( hasWindow ) {
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;
if ( hasWindow ) {
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( /* event */) {
window.removeEventListener( moveEvent, this );
window.removeEventListener( upEvent, this );
this.ondragEnd();
};
return Dragger;
} ) );
/**
* Illustration
*/
( 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 ) {
/* eslint consistent-this: "off" */
item = this;
}
this.dragRotate = item;
this.bindDrag( this.element );
};
Illustration.prototype.dragStart = function( /* event, pointer */) {
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;
} ) );
/**
* PathCommand
*/
( 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;
} ) );
/**
* Shape
*/
( 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;
} ) );
/**
* Group
*/
( 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;
} ) );
/**
* 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.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;
/* eslint key-spacing: "off" */
this.path = [
{ x: -x, y: -y },
{ x: x, y: -y },
{ x: x, y: y },
{ x: -x, y: y },
];
};
return Rect;
} ) );
/**
* 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.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() {
/* eslint
id-length: [ "error", { "min": 2, "exceptions": [ "x", "y" ] }],
key-spacing: "off" */
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;
} ) );
/**
* Ellipse
*/
( 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;
} ) );
/**
* Shape
*/
( 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;
} ) );
/**
* Hemisphere composite shape
*/
( 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( /* options */) {
// 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;
} ) );
/**
* Cylinder composite shape
*/
( 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( /* options */) {
// 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;
} ) );
/**
* Cone composite shape
*/
( 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( /* options */) {
// 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;
} ) );
/**
* Box composite shape
*/
( 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 );
/* eslint-disable no-self-assign */
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 );
};
/* eslint-enable no-self-assign */
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;
} ) );
/**
* Index
*/
( 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 ) {
/* globals define */ // AMD
define( 'zdog', [], root.Zdog );
}
/* eslint-disable max-params */
} )( this, function factory( Zdog, CanvasRenderer, SvgRenderer, Vector, Anchor,
Dragger, Illustration, PathCommand, Shape, Group, Rect, RoundedRect,
Ellipse, Polygon, Hemisphere, Cylinder, Cone, Box ) {
/* eslint-enable max-params */
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;
} );К примеру так:
<script src="js/zdog.js"></script>