You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

rotation3D.js 12 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. var cancelFrame = window.cancelAnimationFrame || window.cancelRequestAnimationFrame;
  2. var requestFrame = window.requestAnimationFrame;
  3. var time = !window.performance || !window.performance.now ?
  4. function () {return +new Date()}:
  5. function () {return performance.now()};
  6. /**
  7. * 计算两点距离
  8. * @param points
  9. * @returns {number}
  10. * distance([{x:0,y:0},{x:1,y:1}]);
  11. */
  12. var distance = function(points) {
  13. var p1=points[0];
  14. var p2=points[1];
  15. var a = p2.x-p1.x;
  16. var b = p2.y-p1.y;
  17. return Math.sqrt(a*a+b*b);
  18. };
  19. /**
  20. * 圆公式
  21. * @param rotation 弧度
  22. * 计算公式:
  23. * Math.PI; //圆周率
  24. * Math.sin(); //正弦 x -左 +右
  25. * Math.cos; //余弦 y -下 +上
  26. */
  27. var circleMath = {
  28. /**
  29. * 根据弧度计算角度
  30. * @param rotation 弧度
  31. * rotation, farScale, xs, xr, ys, yr, itemWidth
  32. */
  33. // parseRotate: function (rotation) {
  34. // return (180 / Math.PI * rotation) - 180;
  35. // },
  36. parseRotate: function (rotation, self) {
  37. var sin = Math.sin(rotation), cos = Math.cos(rotation);
  38. var sin_cos = sin*cos; //得出偏移正负值,从0°向左依次 +-+-
  39. var angle = (180 / Math.PI * rotation) - 180;
  40. var lastAngle = angle;
  41. // console.log('rotation',rotation)
  42. // console.log('sin',sin)
  43. // console.log('cos',cos)
  44. // console.log('sin*cos',sin*cos);
  45. // console.log('统一偏移角度',self.yr * (sin_cos/Math.PI))
  46. lastAngle = angle + (self.yr * (sin_cos/(Math.PI+1)));
  47. return lastAngle;
  48. },
  49. /**
  50. * 计算scale,x,y
  51. * scale 最小尺寸 + ((1 - 最小尺寸) * (sin正弦 + 1) * 0.5)
  52. * x x起点 + (尺寸 * cos余弦 * x半径) - 元素宽度一半
  53. * y y起点 + (尺寸 * sin正弦 * x半径) - 元素宽度一半
  54. * farScale, xs, xr, ys, yr, itemWidth
  55. */
  56. parseSXY: function (rotation, self) {
  57. var farScale=self.farScale;
  58. var itemWidth=self.itemWidth;
  59. var xs=self.xs; var xr=self.xr; var ys=self.ys; var yr=self.yr;
  60. var sin = Math.sin(rotation), cos = Math.cos(rotation);
  61. var scale = farScale + ((1 - farScale) * (sin + 1) * 0.5); //单个尺寸
  62. // 按设置尺寸
  63. // var x = xs + (scale * cos * xr) - (itemWidth * 0.5);
  64. // var y = ys + (scale * sin * yr) - (itemWidth * 0.5);
  65. // 不使用压缩
  66. // var x = xs + (cos * xs) - (itemWidth * 0.5);
  67. // var y = ys + (sin * ys) - (itemWidth * 0.5);
  68. // 使用压缩
  69. var x = xs + (cos * xr) - (itemWidth * 0.5);
  70. var y = ys + (sin * yr) - (itemWidth * 0.5);
  71. var distanceNumber = distance([
  72. {x:self.$rotation.width()/2 - self.$item.width()/2, y:self.$rotation.height()/2 - self.$item.height()/2},
  73. {x:x,y:y}]
  74. );
  75. // console.log({x:self.$rotation.width()/2, y:self.$rotation.height()/2})
  76. // console.log('x,y',x,y)
  77. // console.log('两点距离',distanceNumber)
  78. return {
  79. x: x,
  80. y: y,
  81. scale: scale,
  82. distanceNumber: distanceNumber,
  83. }
  84. },
  85. }
  86. /**
  87. * 3D旋转
  88. * @param id
  89. */
  90. var Rotation3D = window.Rotation3D = function (_opts) {
  91. var self=this;
  92. this.$rotation = $(_opts.id)
  93. this.$lineList = this.$rotation.find('.lineList')
  94. this.$item = this.$rotation.find('.rotation3D__item')
  95. this.$line = this.$rotation.find('.rotation3D__line')
  96. this.itemWidth = this.$item.width();
  97. this.itemHeight = this.$item.height();
  98. this.length = this.$item.length;
  99. // 圆计算
  100. this.rotation = Math.PI / 2; //圆周率/2
  101. this.destRotation = this.rotation;
  102. var xr = this.$rotation.width() * 0.5;
  103. var yr = this.$rotation.height() * 0.5;
  104. var xRadius = _opts.xRadius || 0;
  105. var yRadius = _opts.yRadius || 0;
  106. var opts = Object.assign({
  107. farScale: 1, // 最小尺寸
  108. xs: xr, // x起点
  109. ys: yr, // y起点
  110. xr: xr - xRadius, // x半径-压缩
  111. yr: yr - yRadius, // y半径-压缩
  112. // 播放
  113. autoPlay:false,
  114. autoPlayDelay:3000,
  115. currenIndex:-1,
  116. fps:30,
  117. speed:4,
  118. },_opts)
  119. Object.assign(this, opts)
  120. // 遍历子元素
  121. this.$item.each(function (index) {
  122. $(this).click(function () {
  123. $(this).addClass('active').siblings().removeClass('active')
  124. self.goTo(index)
  125. })
  126. })
  127. // 当前控件进入离开
  128. this.$rotation.mouseenter(function () {
  129. clearInterval(self.autoPlayTimer)
  130. })
  131. this.$rotation.mouseleave(function () {
  132. self.onAutoPlay()
  133. })
  134. this.onAutoPlay()
  135. this.onDrag()
  136. this.render()
  137. }
  138. /**
  139. * item样式
  140. * x x起点 + (尺寸 * 余弦 * x压缩) - 元素宽度一半
  141. * y y起点 + (尺寸 * 正弦 * y压缩) - 元素宽度一半
  142. */
  143. Rotation3D.prototype.itemStyle = function($item, index, rotation) {
  144. console.log("itemStyle=" + rotation + " index=" + index);
  145. var parseSXY = circleMath.parseSXY(rotation, this);
  146. var scale = parseSXY.scale;
  147. var x = parseSXY.x;
  148. var y = parseSXY.y;
  149. var $line = this.$lineList.find('.rotation3D__line').eq(index);
  150. //设置当前子菜单的位置(left,top) = (x,y)
  151. $item.find('.scale').css({
  152. 'transform': `scale(${scale})`,
  153. // 'top': `${this.itemWidth * (1-scale) }`,
  154. })
  155. $item.css({
  156. position: 'absolute',
  157. display: 'inline-block',
  158. // opacity: scale,
  159. 'z-index': parseInt(scale * 100),
  160. 'transform-origin': '0px 0px',
  161. // 'transform': `translate(${x}px, ${y}px) scale(${scale})`,
  162. 'transform': `translate(${x}px, ${y}px)`,
  163. });
  164. /**
  165. * 线样式
  166. */
  167. $line.css({
  168. height:parseSXY.distanceNumber,
  169. })
  170. $line.find('svg').css({
  171. height:parseSXY.distanceNumber,
  172. })
  173. $line.find('.dot1').css({
  174. 'offset-path':`path("M0 ${parseSXY.distanceNumber}, 0 0")`,
  175. })
  176. $line.find('#path1').attr({
  177. 'd':`M0 ${parseSXY.distanceNumber}, 0 0`,
  178. })
  179. $line.find('.dot2').css({
  180. 'offset-path':`path("M0 ${parseSXY.distanceNumber/2}, 0 0")`,
  181. })
  182. $line.find('#path2').attr({
  183. 'd':`M0 ${parseSXY.distanceNumber}, 0 0`,
  184. })
  185. $line.find('.dot3').css({
  186. 'offset-path':`path("M20 ${parseSXY.distanceNumber} S 0 ${parseSXY.distanceNumber/2}, 20 0")`,
  187. })
  188. $line.find('#path3').attr({
  189. 'd':`M20 ${parseSXY.distanceNumber} S 0 ${parseSXY.distanceNumber/2}, 20 0`,
  190. })
  191. $line.find('.dot4').css({
  192. 'offset-path':`path("M20 0 S 40 ${parseSXY.distanceNumber/2}, 20 ${parseSXY.distanceNumber}")`,
  193. })
  194. $line.find('#path4').attr({
  195. 'd':`M20 0 S 40 ${parseSXY.distanceNumber/2}, 20 ${parseSXY.distanceNumber}`,
  196. })
  197. }
  198. /**
  199. * line样式
  200. */
  201. Rotation3D.prototype.lineStyle = function($line, index, rotation) {
  202. var rotate = circleMath.parseRotate(rotation, this)
  203. console.log("lineStyle=" + rotation + " index=" + index);
  204. $line.css({
  205. transform: 'rotate(' + rotate + 'deg)',
  206. })
  207. this.$lineList.css({
  208. // transform: `rotateX(${this.yRadius / 3}deg)`,
  209. })
  210. }
  211. /**
  212. * 旋转至index
  213. */
  214. Rotation3D.prototype.goTo = function (index) {
  215. var self = this;
  216. this.currenIndex = index;
  217. console.log('goTo currenIndex', index);
  218. /**
  219. * 1.计算floatIndex,用于控死amdiff
  220. */
  221. var itemsRotated = this.length * ((Math.PI / 2) - this.rotation) / (2 * Math.PI);
  222. var floatIndex = itemsRotated % this.length;
  223. if (floatIndex < 0) { floatIndex = floatIndex + this.length; }
  224. /**
  225. * 2.计算diff,判断方向正反
  226. */
  227. var diff = index - (floatIndex % this.length);
  228. if (2 * Math.abs(diff) > this.length) {
  229. diff -= (diff > 0) ? this.length : -this.length;
  230. }
  231. // 停止任何正在进行的旋转
  232. this.destRotation += (2 * Math.PI / this.length) * -diff;
  233. this.scheduleNextFrame();
  234. }
  235. /**
  236. * 定时器渐近旋转
  237. */
  238. Rotation3D.prototype.scheduleNextFrame = function () {
  239. var self = this
  240. this.lastTime = time();
  241. // 暂停
  242. var pause = function () {
  243. cancelFrame ? cancelFrame(this.timer) : clearTimeout(self.timer);
  244. self.timer = 0;
  245. }
  246. // 渐进播放
  247. var playFrame = function () {
  248. var rem = self.destRotation - self.rotation;
  249. var now = time(), dt = (now - self.lastTime) * 0.002;
  250. self.lastTime = now;
  251. // console.log('rem',rem)
  252. if (Math.abs(rem) < 0.003) {
  253. self.rotation = self.destRotation;
  254. pause();
  255. } else {
  256. // 渐近地接近目的地
  257. self.rotation = self.destRotation - rem / (1 + (self.speed * dt));
  258. self.scheduleNextFrame();
  259. }
  260. self.render();
  261. }
  262. this.timer = cancelFrame ?
  263. requestFrame(playFrame) :
  264. setTimeout(playFrame, 1000 / this.fps);
  265. }
  266. /**
  267. * 更新
  268. */
  269. Rotation3D.prototype.render = function () {
  270. var self=this;
  271. // 图形间隔:弧度
  272. var spacing = 2 * Math.PI / this.$item.length;
  273. var itemRotation = this.rotation;
  274. var lineRotation = this.rotation + (Math.PI/2);
  275. this.$item.each(function (index) {
  276. self.itemStyle($(this), index, itemRotation)
  277. itemRotation += spacing;
  278. })
  279. this.$line.each(function (index) {
  280. self.lineStyle($(this), index, lineRotation)
  281. lineRotation += spacing;
  282. })
  283. }
  284. /**
  285. * 自动播放
  286. */
  287. Rotation3D.prototype.onAutoPlay = function () {
  288. var self = this;
  289. if (this.autoPlay) {
  290. this.autoPlayTimer = setInterval(function () {
  291. if (self.currenIndex < 0) {
  292. self.currenIndex = self.length - 1
  293. }
  294. console.log("autoPlayTimer....");
  295. self.goTo(self.currenIndex);
  296. self.currenIndex--; //倒叙
  297. }, this.autoPlayDelay)
  298. }
  299. }
  300. /**
  301. * 拖拽
  302. */
  303. Rotation3D.prototype.onDrag = function () {
  304. var self = this;
  305. var startX, startY, moveX, moveY, endX, endY;
  306. console.log("onDrag....");
  307. // 拖拽:三个事件-按下 移动 抬起
  308. //按下
  309. this.$rotation.mousedown(function (e) {
  310. startX = e.pageX; startY = e.pageY;
  311. console.log("mousedown....");
  312. // 移动
  313. $(document).mousemove(function (e) {
  314. // console.log('移动');
  315. endX = e.pageX; endY = e.pageY;
  316. moveX = endX - startX; moveY = endY - startY;
  317. // console.log('x,y',moveX,moveY);
  318. })
  319. // 抬起
  320. $(document).mouseup(function (e) {
  321. endX = e.pageX; endY = e.pageY;
  322. moveX = endX - startX; moveY = endY - startY;
  323. console.log("mouseup....");
  324. // 每40旋转一步
  325. var moveIndex = parseInt(Math.abs(moveX) / 50)
  326. console.log('moveIndex',moveIndex)
  327. if (moveIndex > 0) {
  328. // console.log(moveX<0 ? '向左' : '向右')
  329. if (moveX < 0) { //向左
  330. self.currenIndex = self.currenIndex - moveIndex
  331. play(moveIndex)
  332. } else { //向右
  333. self.currenIndex = self.currenIndex + moveIndex
  334. play(moveIndex)
  335. }
  336. }
  337. // 解绑
  338. $(document).unbind("mousemove");
  339. $(document).unbind("mouseup");
  340. })
  341. })
  342. function play() {
  343. if (self.currenIndex == 0) {
  344. self.currenIndex = self.length - 1
  345. }
  346. self.goTo(self.currenIndex % self.length);
  347. }
  348. }