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); } }