js实现可拖拽旋转三维音乐卡通人物效果代码
代码语言:html
所属分类:三维
代码描述:js实现可拖拽旋转三维音乐卡通人物效果代码
下面为部分代码预览,完整代码请点击下载或在bfwstudio webide中打开
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
html { height: 100%; }
body {
min-height: 100%;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
background: #435;
color: white;
font-family: sans-serif;
text-align: center;
}
canvas {
display: block;
margin: 0 auto;
cursor: move;
}
</style>
</head>
<body>
<div class="container">
<canvas></canvas>
<p>点击拖动或旋转</p>
</div>
<script>
// -------------------------- utils -------------------------- //
var TAU = Math.PI * 2;
function extend( a, b ) {
for ( var prop in b ) {
a[ prop ] = b[ prop ];
}
return a;
}
function lerp( a, b, t ) {
return ( b - a ) * t + a;
}
function modulo( num, div ) {
return ( ( num % div ) + div ) % div;
}
// -------------------------- Vector3 -------------------------- //
function Vector3( position ) {
this.set( position );
}
Vector3.prototype.set = function( pos ) {
pos = Vector3.sanitize( pos );
this.x = pos.x;
this.y = pos.y;
this.z = pos.z;
return this;
};
Vector3.prototype.rotate = function( rotation ) {
if ( !rotation ) {
return;
}
this.rotateZ( rotation.z );
this.rotateY( rotation.y );
this.rotateX( rotation.x );
return this;
};
Vector3.prototype.rotateZ = function( angle ) {
rotateProperty( this, angle, 'x', 'y' );
};
Vector3.prototype.rotateX = function( angle ) {
rotateProperty( this, angle, 'y', 'z' );
};
Vector3.prototype.rotateY = function( angle ) {
rotateProperty( this, angle, 'x', 'z' );
};
function rotateProperty( vec, angle, propA, propB ) {
if ( 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;
}
Vector3.prototype.add = function( vec ) {
if ( !vec ) {
return;
}
vec = Vector3.sanitize( vec );
this.x += vec.x;
this.y += vec.y;
this.z += vec.z;
return this;
};
Vector3.prototype.multiply = function( vec ) {
if ( !vec ) {
return;
}
vec = Vector3.sanitize( vec );
this.x *= vec.x;
this.y *= vec.y;
this.z *= vec.z;
return this;
};
Vector3.prototype.lerp = function( vec, t ) {
this.x = lerp( this.x, vec.x, t );
this.y = lerp( this.y, vec.y, t );
this.z = lerp( this.z, vec.z, t );
return this;
};
// ----- utils ----- //
// add missing properties
Vector3.sanitize = function( vec ) {
vec = vec || {};
vec.x = vec.x || 0;
vec.y = vec.y || 0;
vec.z = vec.z || 0;
return vec;
};
// -------------------------- PathAction -------------------------- //
function PathAction( method, points, previousPoint ) {
this.method = method;
this.points = points.map( mapVectorPoint );
this.renderPoints = points.map( mapVectorPoint );
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 Vector3(), new Vector3() ];
}
}
function mapVectorPoint( point ) {
return new Vector3( point );
}
PathAction.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 );
});
};
PathAction.prototype.transform = function( translation, rotation, scale ) {
this.renderPoints.forEach( function( renderPoint ) {
renderPoint.multiply( scale );
renderPoint.rotate( rotation );
renderPoint.add( translation );
});
};
PathAction.prototype.render = function( ctx ) {
this[ this.method ]( ctx );
};
PathAction.prototype.move = function( ctx ) {
var point = this.renderPoints[0];
ctx.moveTo( point.x, point.y );
};
PathAction.prototype.line = function( ctx ) {
var point = this.renderPoints[0];
ctx.lineTo( point.x, point.y );
};
PathAction.prototype.bezier = function( ctx ) {
var cp0 = this.renderPoints[0];
var cp1 = this.renderPoints[1];
var end = this.renderPoints[2];
ctx.bezierCurveTo( cp0.x, cp0.y, cp1.x, cp1.y, end.x, end.y );
};
PathAction.prototype.arc = function( ctx ) {
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, 9/16 );
cp1.set( end ).lerp( corner, 9/16 );
ctx.bezierCurveTo( cp0.x, cp0.y, cp1.x, cp1.y, end.x, end.y );
};
// -------------------------- Shape -------------------------- //
function Shape( options ) {
this.create( options );
}
Shape.prototype.create = function( options ) {
// default
extend( this, Shape.defaults );
// set options
setOptions( this, options );
this.updatePathActions();
// transform
this.translate = new Vector3( options.translate );
this.rotate = new Vector3( options.rotate );
var scale = extend( { x: 1, y: 1, z: 1 }, options.scale );
this.scale = new Vector3( scale );
// children
this.children = [];
if ( this.addTo ) {
this.addTo.addChild( this );
}
};
Shape.defaults = {
stroke: true,
fill: false,
color: 'black',
lineWidth: 1,
closed: true,
rendering: true,
path: [ {} ],
};
var optionKeys = Object.keys( Shape.defaults ).concat([
'rotate',
'translate',
'scale',
'addTo',
'width',
'height',
]);
function setOptions( shape, options ) {
for ( var key in options ) {
if ( optionKeys.includes( key ) ) {
shape[ key ] = options[ key ];
}
}
}
var actionNames = [
'move',
'line',
'bezier',
'arc',
];
// parse path into PathActions
Shape.prototype.updatePathActions = function() {
var previousPoint;
this.pathActions = 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 ];
var isInstruction = keys.length === 1 && actionNames.includes( method ) &&
Array.isArray( points );
if ( !isInstruction ) {
method = 'line';
points = [ pathPart ];
}
// first action is always move
method = i === 0 ? 'move' : method;
// arcs require previous last point
var pathAction = new PathAction( method, points, previousPoint );
// update previousLastPoint
previousPoint = pathAction.endRenderPoint;
return pathAction;
});
};
Shape.prototype.addChild = function( shape ) {
this.children.push( shape );
};
// ----- update ----- //
Shape.prototype.update = function() {
// update self
this.reset();
// update children
this.children.forEach( function( child ) {
child.update();
});
this.transform( this.translate, this.rotate, this.scale );
};
Shape.prototype.reset = function() {
// reset pathAction render points
this.pathActions.forEach( function( pathAction ) {
pathAction.reset();
});
};
Shape.prototype.transform = function( translation, rotation, scale ) {
// transform points
this.pathActions.forEach( function( pathAction ) {
pathAction.transform( translation, rotation, scale );
});
// transform children
this.children.forEach( function( child ) {
child.transform( translation, rotation, scale );
});
};
Shape.prototype.updateSortValue = function() {
var sortValueTotal = 0;
this.pathActions.forEach( function( pathAction ) {
sortValueTotal += pathAction.endRenderPoint.z;
});
// average sort value of all points
// def not geometrically correct, but works for me
this.sortValue = sortValueTotal / this.pathActions.length;
};
// ----- render ----- //
Shape.prototype.render = function( ctx ) {
var length = this.pathActions.length;
if ( !this.rendering || !length ) {
return;
}
var isDot = length == 1;
if ( isDot ) {
this.renderDot( ctx );
} else {
this.renderPath( ctx );
}
};
// Safari does not render lines with no size, have to render circle instead
Shape.prototype.renderDot = function( ctx ) {
ctx.fillStyle = this.color;
var point = this.pathActions[0].endRenderPoint;
ctx.beginPath();
var radius = this.lineWidth/2;
ctx.arc( point.x, point.y, radius, 0, TAU );
ctx.fill();
};
Shape.prototype.renderPath = function( ctx ) {
// set render properties
ctx.fillStyle = this.color;
ctx.strokeStyle = this.color;
ctx.lineWidth = this.lineWidth;
// render points
ctx.beginPath();
this.pathActions.forEach( function( pathAction ) {
pathAction.render( ctx );
});
var isTwoPoints = this.pathActions.length == 2 &&
this.pathActions[1].method == 'line';
if ( !isTwoPoints && this.closed ) {
ctx.closePath();
}
if ( this.stroke ) {
ctx.stroke();
}
if ( this.fill ) {
ctx.fill();
}
};
// return Array of self & all child shapes
Shape.prototype.getShapes = function() {
var shapes = [ this ];
this.children.forEach( function( child ) {
var childShapes = child.getShapes();
shapes = shapes.concat( childShapes );
});
return shapes;
};
Shape.prototype.copy = function( options ) {
// copy options
var shapeOptions = {};
optionKeys.forEach( function( key ) {
shapeOptions[ key ] = this[ key ];
}, this );
// add set options
setOptions( shapeOptions, options );
var ShapeClass = this.constructor;
return new ShapeClass( shapeOptions );
};
// -------------------------- Ellipse -------------------------- //
function Ellipse( options ) {
options = this.setPath( options );
// always keep open
// fixes overlap bug when lineWidth is greater than radius
options.closed = false;
this.create( options );
}
Ellipse.prototype = Object.create( Shape.prototype );
Ellipse.prototype.constructor = Ellipse;
Ellipse.prototype.setPath = function( options ) {
var w = options.width/2;
var h = options.height/2;
options.path = [
{ x: 0, y: -h },
{ arc: [ // top right
.........完整代码请登录后点击上方下载按钮下载查看
网友评论0