|
|
@@ -0,0 +1,380 @@ |
|
|
|
var cancelFrame = window.cancelAnimationFrame || window.cancelRequestAnimationFrame; |
|
|
|
var requestFrame = window.requestAnimationFrame; |
|
|
|
var time = !window.performance || !window.performance.now ? |
|
|
|
function () {return +new Date()}: |
|
|
|
function () {return performance.now()}; |
|
|
|
|
|
|
|
/** |
|
|
|
* 计算两点距离 |
|
|
|
* @param points |
|
|
|
* @returns {number} |
|
|
|
* distance([{x:0,y:0},{x:1,y:1}]); |
|
|
|
*/ |
|
|
|
var distance = function(points) { |
|
|
|
var p1=points[0]; |
|
|
|
var p2=points[1]; |
|
|
|
var a = p2.x-p1.x; |
|
|
|
var b = p2.y-p1.y; |
|
|
|
return Math.sqrt(a*a+b*b); |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* 圆公式 |
|
|
|
* @param rotation 弧度 |
|
|
|
* 计算公式: |
|
|
|
* Math.PI; //圆周率 |
|
|
|
* Math.sin(); //正弦 x -左 +右 |
|
|
|
* Math.cos; //余弦 y -下 +上 |
|
|
|
*/ |
|
|
|
var circleMath = { |
|
|
|
/** |
|
|
|
* 根据弧度计算角度 |
|
|
|
* @param rotation 弧度 |
|
|
|
* rotation, farScale, xs, xr, ys, yr, itemWidth |
|
|
|
*/ |
|
|
|
// parseRotate: function (rotation) { |
|
|
|
// return (180 / Math.PI * rotation) - 180; |
|
|
|
// }, |
|
|
|
parseRotate: function (rotation, self) { |
|
|
|
var sin = Math.sin(rotation), cos = Math.cos(rotation); |
|
|
|
var sin_cos = sin*cos; //得出偏移正负值,从0°向左依次 +-+- |
|
|
|
var angle = (180 / Math.PI * rotation) - 180; |
|
|
|
var lastAngle = angle; |
|
|
|
|
|
|
|
// console.log('rotation',rotation) |
|
|
|
// console.log('sin',sin) |
|
|
|
// console.log('cos',cos) |
|
|
|
// console.log('sin*cos',sin*cos); |
|
|
|
// console.log('统一偏移角度',self.yr * (sin_cos/Math.PI)) |
|
|
|
|
|
|
|
lastAngle = angle + (self.yr * (sin_cos/(Math.PI+1))); |
|
|
|
|
|
|
|
return lastAngle; |
|
|
|
}, |
|
|
|
/** |
|
|
|
* 计算scale,x,y |
|
|
|
* scale 最小尺寸 + ((1 - 最小尺寸) * (sin正弦 + 1) * 0.5) |
|
|
|
* x x起点 + (尺寸 * cos余弦 * x半径) - 元素宽度一半 |
|
|
|
* y y起点 + (尺寸 * sin正弦 * x半径) - 元素宽度一半 |
|
|
|
* farScale, xs, xr, ys, yr, itemWidth |
|
|
|
*/ |
|
|
|
parseSXY: function (rotation, self) { |
|
|
|
var farScale=self.farScale; |
|
|
|
var itemWidth=self.itemWidth; |
|
|
|
var xs=self.xs; var xr=self.xr; var ys=self.ys; var yr=self.yr; |
|
|
|
var sin = Math.sin(rotation), cos = Math.cos(rotation); |
|
|
|
var scale = farScale + ((1 - farScale) * (sin + 1) * 0.5); //单个尺寸 |
|
|
|
|
|
|
|
// 按设置尺寸 |
|
|
|
// var x = xs + (scale * cos * xr) - (itemWidth * 0.5); |
|
|
|
// var y = ys + (scale * sin * yr) - (itemWidth * 0.5); |
|
|
|
// 不使用压缩 |
|
|
|
// var x = xs + (cos * xs) - (itemWidth * 0.5); |
|
|
|
// var y = ys + (sin * ys) - (itemWidth * 0.5); |
|
|
|
// 使用压缩 |
|
|
|
var x = xs + (cos * xr) - (itemWidth * 0.5); |
|
|
|
var y = ys + (sin * yr) - (itemWidth * 0.5); |
|
|
|
var distanceNumber = distance([ |
|
|
|
{x:self.$rotation.width()/2 - self.$item.width()/2, y:self.$rotation.height()/2 - self.$item.height()/2}, |
|
|
|
{x:x,y:y}] |
|
|
|
); |
|
|
|
|
|
|
|
// console.log({x:self.$rotation.width()/2, y:self.$rotation.height()/2}) |
|
|
|
// console.log('x,y',x,y) |
|
|
|
// console.log('两点距离',distanceNumber) |
|
|
|
|
|
|
|
return { |
|
|
|
x: x, |
|
|
|
y: y, |
|
|
|
scale: scale, |
|
|
|
distanceNumber: distanceNumber, |
|
|
|
} |
|
|
|
}, |
|
|
|
} |
|
|
|
/** |
|
|
|
* 3D旋转 |
|
|
|
* @param id |
|
|
|
*/ |
|
|
|
var Rotation3D = window.Rotation3D = function (_opts) { |
|
|
|
var self=this; |
|
|
|
this.$rotation = $(_opts.id) |
|
|
|
this.$lineList = this.$rotation.find('.lineList') |
|
|
|
this.$item = this.$rotation.find('.rotation3D__item') |
|
|
|
this.$line = this.$rotation.find('.rotation3D__line') |
|
|
|
this.itemWidth = this.$item.width(); |
|
|
|
this.itemHeight = this.$item.height(); |
|
|
|
this.length = this.$item.length; |
|
|
|
// 圆计算 |
|
|
|
this.rotation = Math.PI / 2; //圆周率/2 |
|
|
|
this.destRotation = this.rotation; |
|
|
|
|
|
|
|
var xr = this.$rotation.width() * 0.5; |
|
|
|
var yr = this.$rotation.height() * 0.5; |
|
|
|
var xRadius = _opts.xRadius || 0; |
|
|
|
var yRadius = _opts.yRadius || 0; |
|
|
|
|
|
|
|
var opts = Object.assign({ |
|
|
|
farScale: 1, // 最小尺寸 |
|
|
|
xs: xr, // x起点 |
|
|
|
ys: yr, // y起点 |
|
|
|
xr: xr - xRadius, // x半径-压缩 |
|
|
|
yr: yr - yRadius, // y半径-压缩 |
|
|
|
// 播放 |
|
|
|
autoPlay:false, |
|
|
|
autoPlayDelay:3000, |
|
|
|
currenIndex:-1, |
|
|
|
fps:30, |
|
|
|
speed:4, |
|
|
|
},_opts) |
|
|
|
Object.assign(this, opts) |
|
|
|
|
|
|
|
// 遍历子元素 |
|
|
|
this.$item.each(function (index) { |
|
|
|
$(this).click(function () { |
|
|
|
$(this).addClass('active').siblings().removeClass('active') |
|
|
|
self.goTo(index) |
|
|
|
}) |
|
|
|
}) |
|
|
|
// 当前控件进入离开 |
|
|
|
this.$rotation.mouseenter(function () { |
|
|
|
clearInterval(self.autoPlayTimer) |
|
|
|
}) |
|
|
|
this.$rotation.mouseleave(function () { |
|
|
|
self.onAutoPlay() |
|
|
|
}) |
|
|
|
|
|
|
|
this.onAutoPlay() |
|
|
|
this.onDrag() |
|
|
|
this.render() |
|
|
|
|
|
|
|
} |
|
|
|
/** |
|
|
|
* item样式 |
|
|
|
* x x起点 + (尺寸 * 余弦 * x压缩) - 元素宽度一半 |
|
|
|
* y y起点 + (尺寸 * 正弦 * y压缩) - 元素宽度一半 |
|
|
|
*/ |
|
|
|
Rotation3D.prototype.itemStyle = function($item, index, rotation) { |
|
|
|
console.log("itemStyle=" + rotation + " index=" + index); |
|
|
|
var parseSXY = circleMath.parseSXY(rotation, this); |
|
|
|
var scale = parseSXY.scale; |
|
|
|
var x = parseSXY.x; |
|
|
|
var y = parseSXY.y; |
|
|
|
var $line = this.$lineList.find('.rotation3D__line').eq(index); |
|
|
|
|
|
|
|
//设置当前子菜单的位置(left,top) = (x,y) |
|
|
|
$item.find('.scale').css({ |
|
|
|
'transform': `scale(${scale})`, |
|
|
|
// 'top': `${this.itemWidth * (1-scale) }`, |
|
|
|
}) |
|
|
|
$item.css({ |
|
|
|
position: 'absolute', |
|
|
|
display: 'inline-block', |
|
|
|
// opacity: scale, |
|
|
|
'z-index': parseInt(scale * 100), |
|
|
|
'transform-origin': '0px 0px', |
|
|
|
// 'transform': `translate(${x}px, ${y}px) scale(${scale})`, |
|
|
|
'transform': `translate(${x}px, ${y}px)`, |
|
|
|
}); |
|
|
|
|
|
|
|
/** |
|
|
|
* 线样式 |
|
|
|
*/ |
|
|
|
$line.css({ |
|
|
|
height:parseSXY.distanceNumber, |
|
|
|
}) |
|
|
|
$line.find('svg').css({ |
|
|
|
height:parseSXY.distanceNumber, |
|
|
|
}) |
|
|
|
$line.find('.dot1').css({ |
|
|
|
'offset-path':`path("M0 ${parseSXY.distanceNumber}, 0 0")`, |
|
|
|
}) |
|
|
|
$line.find('#path1').attr({ |
|
|
|
'd':`M0 ${parseSXY.distanceNumber}, 0 0`, |
|
|
|
}) |
|
|
|
|
|
|
|
$line.find('.dot2').css({ |
|
|
|
'offset-path':`path("M0 ${parseSXY.distanceNumber/2}, 0 0")`, |
|
|
|
}) |
|
|
|
$line.find('#path2').attr({ |
|
|
|
'd':`M0 ${parseSXY.distanceNumber}, 0 0`, |
|
|
|
}) |
|
|
|
|
|
|
|
$line.find('.dot3').css({ |
|
|
|
'offset-path':`path("M20 ${parseSXY.distanceNumber} S 0 ${parseSXY.distanceNumber/2}, 20 0")`, |
|
|
|
}) |
|
|
|
$line.find('#path3').attr({ |
|
|
|
'd':`M20 ${parseSXY.distanceNumber} S 0 ${parseSXY.distanceNumber/2}, 20 0`, |
|
|
|
}) |
|
|
|
|
|
|
|
$line.find('.dot4').css({ |
|
|
|
'offset-path':`path("M20 0 S 40 ${parseSXY.distanceNumber/2}, 20 ${parseSXY.distanceNumber}")`, |
|
|
|
}) |
|
|
|
$line.find('#path4').attr({ |
|
|
|
'd':`M20 0 S 40 ${parseSXY.distanceNumber/2}, 20 ${parseSXY.distanceNumber}`, |
|
|
|
}) |
|
|
|
|
|
|
|
} |
|
|
|
/** |
|
|
|
* line样式 |
|
|
|
*/ |
|
|
|
Rotation3D.prototype.lineStyle = function($line, index, rotation) { |
|
|
|
var rotate = circleMath.parseRotate(rotation, this) |
|
|
|
console.log("lineStyle=" + rotation + " index=" + index); |
|
|
|
|
|
|
|
$line.css({ |
|
|
|
transform: 'rotate(' + rotate + 'deg)', |
|
|
|
}) |
|
|
|
this.$lineList.css({ |
|
|
|
// transform: `rotateX(${this.yRadius / 3}deg)`, |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 旋转至index |
|
|
|
*/ |
|
|
|
Rotation3D.prototype.goTo = function (index) { |
|
|
|
var self = this; |
|
|
|
this.currenIndex = index; |
|
|
|
console.log('goTo currenIndex', index); |
|
|
|
/** |
|
|
|
* 1.计算floatIndex,用于控死amdiff |
|
|
|
*/ |
|
|
|
var itemsRotated = this.length * ((Math.PI / 2) - this.rotation) / (2 * Math.PI); |
|
|
|
var floatIndex = itemsRotated % this.length; |
|
|
|
if (floatIndex < 0) { floatIndex = floatIndex + this.length; } |
|
|
|
|
|
|
|
/** |
|
|
|
* 2.计算diff,判断方向正反 |
|
|
|
*/ |
|
|
|
var diff = index - (floatIndex % this.length); |
|
|
|
if (2 * Math.abs(diff) > this.length) { |
|
|
|
diff -= (diff > 0) ? this.length : -this.length; |
|
|
|
} |
|
|
|
// 停止任何正在进行的旋转 |
|
|
|
this.destRotation += (2 * Math.PI / this.length) * -diff; |
|
|
|
this.scheduleNextFrame(); |
|
|
|
|
|
|
|
} |
|
|
|
/** |
|
|
|
* 定时器渐近旋转 |
|
|
|
*/ |
|
|
|
Rotation3D.prototype.scheduleNextFrame = function () { |
|
|
|
var self = this |
|
|
|
this.lastTime = time(); |
|
|
|
// 暂停 |
|
|
|
var pause = function () { |
|
|
|
cancelFrame ? cancelFrame(this.timer) : clearTimeout(self.timer); |
|
|
|
self.timer = 0; |
|
|
|
} |
|
|
|
// 渐进播放 |
|
|
|
var playFrame = function () { |
|
|
|
var rem = self.destRotation - self.rotation; |
|
|
|
var now = time(), dt = (now - self.lastTime) * 0.002; |
|
|
|
self.lastTime = now; |
|
|
|
// console.log('rem',rem) |
|
|
|
|
|
|
|
if (Math.abs(rem) < 0.003) { |
|
|
|
self.rotation = self.destRotation; |
|
|
|
pause(); |
|
|
|
} else { |
|
|
|
// 渐近地接近目的地 |
|
|
|
self.rotation = self.destRotation - rem / (1 + (self.speed * dt)); |
|
|
|
self.scheduleNextFrame(); |
|
|
|
} |
|
|
|
self.render(); |
|
|
|
} |
|
|
|
|
|
|
|
this.timer = cancelFrame ? |
|
|
|
requestFrame(playFrame) : |
|
|
|
setTimeout(playFrame, 1000 / this.fps); |
|
|
|
} |
|
|
|
/** |
|
|
|
* 更新 |
|
|
|
*/ |
|
|
|
Rotation3D.prototype.render = function () { |
|
|
|
var self=this; |
|
|
|
|
|
|
|
// 图形间隔:弧度 |
|
|
|
var spacing = 2 * Math.PI / this.$item.length; |
|
|
|
var itemRotation = this.rotation; |
|
|
|
var lineRotation = this.rotation + (Math.PI/2); |
|
|
|
|
|
|
|
this.$item.each(function (index) { |
|
|
|
self.itemStyle($(this), index, itemRotation) |
|
|
|
itemRotation += spacing; |
|
|
|
}) |
|
|
|
this.$line.each(function (index) { |
|
|
|
self.lineStyle($(this), index, lineRotation) |
|
|
|
lineRotation += spacing; |
|
|
|
}) |
|
|
|
} |
|
|
|
/** |
|
|
|
* 自动播放 |
|
|
|
*/ |
|
|
|
Rotation3D.prototype.onAutoPlay = function () { |
|
|
|
var self = this; |
|
|
|
|
|
|
|
if (this.autoPlay) { |
|
|
|
this.autoPlayTimer = setInterval(function () { |
|
|
|
if (self.currenIndex < 0) { |
|
|
|
self.currenIndex = self.length - 1 |
|
|
|
} |
|
|
|
console.log("autoPlayTimer...."); |
|
|
|
self.goTo(self.currenIndex); |
|
|
|
self.currenIndex--; //倒叙 |
|
|
|
}, this.autoPlayDelay) |
|
|
|
} |
|
|
|
} |
|
|
|
/** |
|
|
|
* 拖拽 |
|
|
|
*/ |
|
|
|
Rotation3D.prototype.onDrag = function () { |
|
|
|
var self = this; |
|
|
|
var startX, startY, moveX, moveY, endX, endY; |
|
|
|
console.log("onDrag...."); |
|
|
|
// 拖拽:三个事件-按下 移动 抬起 |
|
|
|
//按下 |
|
|
|
this.$rotation.mousedown(function (e) { |
|
|
|
startX = e.pageX; startY = e.pageY; |
|
|
|
console.log("mousedown...."); |
|
|
|
// 移动 |
|
|
|
$(document).mousemove(function (e) { |
|
|
|
// console.log('移动'); |
|
|
|
endX = e.pageX; endY = e.pageY; |
|
|
|
moveX = endX - startX; moveY = endY - startY; |
|
|
|
// console.log('x,y',moveX,moveY); |
|
|
|
}) |
|
|
|
// 抬起 |
|
|
|
$(document).mouseup(function (e) { |
|
|
|
endX = e.pageX; endY = e.pageY; |
|
|
|
moveX = endX - startX; moveY = endY - startY; |
|
|
|
console.log("mouseup...."); |
|
|
|
// 每40旋转一步 |
|
|
|
var moveIndex = parseInt(Math.abs(moveX) / 50) |
|
|
|
console.log('moveIndex',moveIndex) |
|
|
|
if (moveIndex > 0) { |
|
|
|
// console.log(moveX<0 ? '向左' : '向右') |
|
|
|
if (moveX < 0) { //向左 |
|
|
|
self.currenIndex = self.currenIndex - moveIndex |
|
|
|
play(moveIndex) |
|
|
|
} else { //向右 |
|
|
|
self.currenIndex = self.currenIndex + moveIndex |
|
|
|
play(moveIndex) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 解绑 |
|
|
|
$(document).unbind("mousemove"); |
|
|
|
$(document).unbind("mouseup"); |
|
|
|
}) |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
function play() { |
|
|
|
if (self.currenIndex == 0) { |
|
|
|
self.currenIndex = self.length - 1 |
|
|
|
} |
|
|
|
self.goTo(self.currenIndex % self.length); |
|
|
|
} |
|
|
|
|
|
|
|
} |