自上次发布用纯原生态javascript+css3 写的3D魔方动画旋扭特效后,说要加上事件的,结果一直忙直到现在才得以有空,故赶紧抽时间赶出这篇文章。上次也因为时间原因只是粘出代码,没有详细解释一下3D魔方具体的实现原理和思想,我个人觉得程序语言只是实现效果的一种工具而已,而最具灵魂性的东西就是实现的原理和算法,只要掌握原理和算法,才能举一反三,才能更灵活的变通,才能如何加上自己的东西进行进一步的改善和创新。故这篇文章不仅要给出代码还会尽量的用文字来阐述一下实现的原理。现实中魔方就是由一个个的小的立方体组成一个大的立方体,共六个面,六中颜色,然后旋转其中的指定的可移动的面来实现不通颜色面的组合。下面我们就按下面的几个步骤来阐述一下。
第一步:创建单个小立方体对象
//单个立方体对象 function Cube(opts) { opts = opts || {}; this.parent = opts.parent; //插入到哪里 this.browserPrefix = opts.browserPrefix; this.width = opts.width; this.height = opts.height; this.cubZ = opts.cubZ; this.face = opts.face; this.row = opts.row; this.column = opts.column; this.index=opts.index; this.offsetX = this.column * (this.width + opts.marginX); // this.offsetY = this.row * (this.height + opts.marginY); // this.offsetZ = this.face * (this.cubZ + opts.marginZ); // this.positiveZ = this.cubZ / 2; this.negativeZ = -this.cubZ / 2; this.cubFaceInfo = opts.cubFaceInfo; this.dimension = opts.dimension; this.centerX = (this.dimension * this.width + (this.dimension - 1) * opts.marginX) / 2; this.centerY = (this.dimension * this.height + (this.dimension - 1) * opts.marginY) / 2; this.centerZ = (this.dimension * this.cubZ + (this.dimension - 1) * opts.marginZ) / 2; this.translateX = this.offsetX - this.centerX; //把中心点设为原点 this.translateY = this.offsetY - this.centerY; // this.translateZ = this.cubZ / 2 + this.offsetZ - this.centerZ; //offsetZ按上面计算应该跟x,y在一个平面上即后面,但实际上由于要形成立方体,在Z轴上已经后退了cubZ/2个距离,故为和上面保持一致在一个面上,这里需要再加回cubZ/2个距离,使默认的xyz原点都在一个平面上即立方体后面左上角三维坐标系,以这个点作为参考点平移和设置旋转原点 this.cubeFace = []; this.rotateTransfrom = ""; this.init(); } Cube.prototype = { init: function () { this.createCubeBox(); this.createFront(); this.createBack(); this.createTop(); this.createBottom(); this.createLeft(); this.createRight(); }, createCubeBox: function () { this.Box = document.createElement('div'); this.Box.style.width = this.width + "px"; this.Box.style.height = this.height + "px"; this.Box.style.left = "50%"; this.Box.style.top = "50%"; this.Box.style.position = "absolute"; this.Box.style[this.browserPrefix + "TransformStyle"] = "preserve-3d"; this.Box.style[this.browserPrefix + "Perspective"] = "0"; // this.Scene.style[this.browserPrefix + "backfaceVisibility"] = "hidden"; this.intalTransform = "translateZ(" + this.translateZ + "px) translateX(" + this.translateX + "px) translateY(" + this.translateY + "px)"; this.Box.style[this.browserPrefix + "Transform"] = this.intalTransform; this.Box.style[this.browserPrefix + "TransformOrigin"] = "" + (-this.translateX) + "px " + (-this.translateY) + "px " + (-this.translateZ) + "px"; this.Box.index=this.index; this.parent.appendChild(this.Box); this.x = window.getComputedStyle(this.Box).getPropertyValue('left'); this.y = window.getComputedStyle(this.Box).getPropertyValue('top'); this.matrix3d = window.getComputedStyle(this.Box).getPropertyValue('transform'); }, createFace: function () { var face = document.createElement('div'); face.style.margin = 0; face.style.position = "absolute"; face.style.width = this.width + "px"; face.style.height = this.height + "px"; face.style.textAlign="center"; face.style.lineHeight=this.height + "px"; // face.innerHTML=this.index; return face; }, createFront: function () { var face = this.createFace(); face.style[this.browserPrefix + "Transform"] = "translateZ(" + this.positiveZ + "px) "; face.translateZ = this.positiveZ; //给生成控制面板用 this.cubeFace.push(face); this.front = face; this.Box.appendChild(face); }, createBack: function () { var face = this.createFace(); face.style[this.browserPrefix + "Transform"] = "translateZ(" + this.negativeZ + "px) "; face.translateZ = this.negativeZ; //给生成控制面板用 this.cubeFace.push(face); this.back = face; this.Box.appendChild(face); }, createTop: function () { var face = this.createFace(); face.style[this.browserPrefix + "Transform"] = "rotateX(90deg) translateZ(" + this.positiveZ + "px) "; face.translateZ = this.positiveZ; //给生成控制面板用 this.cubeFace.push(face); this.top = face; this.Box.appendChild(face); }, createBottom: function () { var face = this.createFace(); face.style[this.browserPrefix + "Transform"] = "rotateX(90deg) translateZ(" + this.negativeZ + "px) "; face.translateZ = this.negativeZ; //给生成控制面板用 this.cubeFace.push(face); this.bottom = face; this.Box.appendChild(face); }, createLeft: function () { var face = this.createFace(); face.style[this.browserPrefix + "Transform"] = "rotateY(90deg) translateZ(" + this.negativeZ + "px) "; face.translateZ = this.negativeZ; //给生成控制面板用 this.cubeFace.push(face); this.left = face; this.Box.appendChild(face); }, createRight: function () { var face = this.createFace(); face.style[this.browserPrefix + "Transform"] = "rotateY(90deg) translateZ(" + (this.positiveZ) + "px) "; face.translateZ = this.positiveZ; //给生成控制面板用 this.cubeFace.push(face); this.right = face; this.Box.appendChild(face); } }
这段代码是用于创建魔方的单个小立方体的对象,这里小立方体由六个DIV元素组成,分别标识为前(front),后(back),左(left),右(right),上(top),下(bottom)六个对象;
六个面放在一个设置了TransformStyle样式的BoxDiv中形成一个3D效果的小立方体,然后再根据传入参数通过Transform,TransformOrigin计算和设置该立方体在整个魔方中的位置。
第二步:给魔方的6个面着色
对于上一步创建的小立方体是没有着色的,因为对于要着色的面必须是对应魔方的对外的六个面上的才需要着色,而正常的逻辑是对于小立方体而言,处于魔方定点上的四个立方体有三个面要着色,棱上的立方体需要两个面着色,而在面中间的立方体则只需要一个面着色,然而按这个逻辑去着色是很麻烦的事情,要找出四个顶点上的,棱上的,面中间的立方体,而对于不同阶数魔方来说,棱上和面中间的立方体数量也是不同的,计算起来会比较复杂,所以这里着色采用的另一种方式:
drawBackGroundColor: function () { for (var face in this.cubFaceInfo) { if (face == "inner") { this.setInnerBKColor(this.cubFaceInfo[face].backGround); } else { var cube = this.getCubesByFace(face); for (var i = 0, len = cube.length; i < len; i++) { var controlPanel = this.getControlPanel(cube[i].Box, cube[i][face]); this.createController(controlPanel, face, cube[i].index); //创建对应的控制面板 cube[i][face].style.background = this.cubFaceInfo[face].backGround; this.bindMouseEvent(cube[i][face], face); //绑定触发和解除控制面板的事件 } } } }
从上面代码看出,魔方有6个面,而组成它的小立方体也是6面,所以比如我要着色魔方的前面(front)时我只要找到组成魔方前面的这些立方体对每个立方体的前面(front)着对应的色即可,其他的面也是照此类同,至于立方体的处于魔方内部的其他面上的颜色则便是处了已被着色面的剩余的面,给他们统一着一个色便可。但是如何获取指定面上所有立方体呢?看下面的代码:
getCubesByFace: function (face) { switch (face) { case "front": return this.getZFace(this.dimension); case "back": return this.getZFace(1); case "left": return this.getXFace(1); case "right": return this.getXFace(this.dimension); case "top": return this.getYFace(1); case "bottom": return this.getYFace(this.dimension); } }
这里的方法里又涉及到了我个人认为在整个系统里面比较重要的几个方法:
getZFace: function (zIndex) { var zFace = []; if (zIndex < 1 || zIndex > this.dimension) return null; for (var i = (zIndex - 1) * this.faceCount; i < zIndex * this.faceCount; i++) { zFace.push(this.cubes[i]); } return zFace; }, getXFace: function (xIndex) { var xFace = []; if (xIndex < 1 || xIndex > this.dimension) return null; for (var i = 0; i < this.count; i++) { if (i % this.dimension == 0) xFace.push(this.cubes[i + xIndex - 1]); } return xFace; }, getYFace: function (yIndex) { var yFace = []; if (yIndex < 1 || yIndex > this.dimension) return null; for (var i = 0; i < this.count; i++) { if (i % this.faceCount == (yIndex - 1) * this.dimension) { for (var j = 0; j < this.dimension; j++) yFace.push(this.cubes[i + j]); } } return yFace; }
getZFace:指的是在Z轴上指定索引的面上的所有立方体,getYFace:指的是在Y轴上指定索引的面上的所有立方体,getXFace:指的是在X轴上指定索引的面上的所有立方体,比如对于一个3阶的魔方对应的索引都是从1到3,如下图所示:
第三步:创建面旋转时动画效果,绑定动画结束事件
findKeyframesRule: function (rule) { var ruleName = this.browserPrefix == "" ? "KEYFRAMES_RULE" : this.browserPrefix.toUpperCase() + "_KEYFRAMES_RULE"; var ss = document.styleSheets; for (var i = 0; i < ss.length; ++i) { for (var j = 0; j < ss[i].cssRules.length; ++j) { if (ss[i].cssRules[j].type == window.CSSRule[ruleName] && ss[i].cssRules[j].name == rule) { return ss[i].cssRules[j]; } } } return null; }, createKeyframesRule: function (rule, startStatus, endStatus) { var prefix = this.browserPrefix == "" ? "" : "-" + this.browserPrefix + "-"; var sheet; if (document.styleSheets.length < 1) sheet = this.createSheets(); else sheet = document.styleSheets[0]; var selectorText = "@" + prefix + "keyframes " + rule; var cssText = "0% { " + prefix + "transform: " + startStatus + "; } 100% { " + prefix + "transform: " + endStatus + "; }" if (sheet.insertRule) { sheet.insertRule(selectorText + "{" + cssText + "}", 0); } else if (sheet.addRule) {//兼容IE sheet.addRule(selectorText, cssText, 0); } }, removeKeyframeRule: function (keyframes) { var length = keyframes.cssRules.length; var keyframeString = []; for (var i = 0; i < length; i++) { keyframeString.push(keyframes.cssRules[i].keyText); } //移除动画帧规则 for (var i = 0, j = length; i < j; i++) { if (this.browserPrefix == "webkit" || this.browserPrefix == "Moz") keyframes.deleteRule(keyframeString[i]); else keyframes.deleteRule(i); //兼容IE } }, changeAnimationStatus: function (animationName, startStatus, endStatus) { var keyframes = this.findKeyframesRule(animationName); this.removeKeyframeRule(keyframes); //重新设置帧规则 var prefix = this.browserPrefix == "" ? "" : "-" + this.browserPrefix + "-"; keyframes.appendRule("0% { " + prefix + "transform: " + startStatus + "; }"); keyframes.appendRule("100% { " + prefix + "transform: " + endStatus + "; }"); }, createSheets: function () { // 创建 <style> 标签 var style = document.createElement("style"); // 可以添加一个媒体(/媒体查询,media query)属性 // style.setAttribute("media", "screen") // style.setAttribute("media", "only screen and (max-width : 1024px)") // 对WebKit hack :( style.appendChild(document.createTextNode("")); // 将 <style> 元素加到页面中 document.head.appendChild(style); return style.sheet; }
这段代码分别对应的是查找帧规则,创建帧规则,移除帧规则,更改帧规则和动态创建样式表方法,说明一下这里的旋转时所采用的动画是采用动画帧的方式实现的,所以要通过以上方法,动态为每一此旋转的动作创建或更改帧规则来实现动画的效果。
timerFun: function () { var _this = cache.magicCube; if (_this.isRunning >= _this.dimension) { for (var i = 0, len = _this.cubeMoveQueue.length; i < len; i++) { var animation = _this.cubeMoveQueue.shift(); animation.cube.Box.style[_this.browserPrefix + "Animation"] = animation.rule + " " + _this.delay + "s linear 1"; // Chrome, Safari 和 Opera 代码 } } }
这个方法就是用于从对应的动画队列中获取要播放的动画对象,应用动画规则,开始动画的播放。
bindAnimationEvent: function () { var loopMove = function () { cache.magicCube.isRunning--; //由于按组转动,顾要等组成员都完成再进行新的动画 if (cache.magicCube.isRunning == 0) cache.magicCube.animationEnd(); } for (var i = 0; i < this.count; i++) { this.prefixedEvent(this.cubes[i].Box, "AnimationEnd", loopMove, true); } cache.magicCube = this; //缓存,避免内存泄露 }
这个方法是绑定的动画播放结束事件。
第四步:旋转魔方的指定面
moveMagicCube: function () { if (this.cubes.length < 1) return; //var cubes = this.getYFace(2); //for (var i = 0, len = cubes.length; i < len; i++) { // cubes[i].Box.className = "rotate"; //} //随机产生3D转动方向 this.isRunning = 0; var direct = this.random(1, 3), rotateDirect = "", getFaceFun; // direct=3; switch (direct) { case 1: rotateDirect = "rotateX"; getFaceFun = this.getXFace; break; case 2: rotateDirect = "rotateY"; getFaceFun = this.getYFace; break; case 3: rotateDirect = "rotateZ"; getFaceFun = this.getZFace; break; } this.rotateFace = rotateDirect; this.cubeRotateStatus = []; for (var i = 1; i <= this.dimension; i++) { var status = this.random(0, 2); this.cubeRotateStatus.push(status); switch (status) { case 0: break; //不转动 case 1: this.rotateBox(this.angle, rotateDirect, i, getFaceFun.call(this, i)); break; //正向转动90 case 2: this.rotateBox(-this.angle, rotateDirect, i, getFaceFun.call(this, i)); break; //反向转动90 } } var flag = false; for (var i = 0, len = this.cubeRotateStatus.length; i < len; i++) { if (this.cubeRotateStatus[i]) { flag = true; break; } } if (!flag) {//一个都没转的情况 则强制补充一个 var index = this.random(1, this.dimension); this.rotateBox(this.angle, rotateDirect, index, getFaceFun.call(this, index)); //正向转动90 this.cubeRotateStatus[index - 1] = 1; //全都不转动 默认选出一个 使其正向转动指定度数 } setTimeout(this.timerFun, 100); this.rollMoveStack.push({ rotateFace: this.rotateFace, cubeRotateStatus: this.cubeRotateStatus }); //记录动作状态 if (this.statusCallBack != null && typeof (this.statusCallBack) == "function") this.statusCallBack(this.rollMoveStack.length); if (this.rollMoveStack.length == this.rollbackPoint)//判断当达到阀值时切换动作方向为回归 { if (this.isRandomConfuse) this.autoPlay = false;//如果是随机打乱模式则终止 else this.moveDirect = false;//否则反方向回归 } }
这个方法将随机的产生要旋转那个轴上的那几个面以及每个面是正向还是反向旋转的控制参数,然后调用
rotateBox: function (angle, rotateDirect, faceIndex, cubes) { if (cubes != null) { var startStatus = rotateDirect + "(0deg)", endStatus = rotateDirect + "(" + angle + "deg)"; // this.changeAnimationStatus("mydhua", startStatus, endStatus) for (var i = 0, len = cubes.length; i < len; i++) { var ruleName = "roateRule" + faceIndex + i; this.isRunning++; //组成员转动统计 //if (cubes[i].rotateTransfrom != "") // startStatus = cubes[i].rotateTransfrom; cubes[i].rotateTransfrom = endStatus; if (this.findKeyframesRule(ruleName) == null) this.createKeyframesRule(ruleName, cubes[i].intalTransform + " " + startStatus, cubes[i].intalTransform + " " + endStatus); else this.changeAnimationStatus(ruleName, cubes[i].intalTransform + " " + startStatus, cubes[i].intalTransform + " " + endStatus); cubes[i].Box.style[this.browserPrefix + "AnimationName"] = "none"; this.cubeMoveQueue.push({ cube: cubes[i], rule: ruleName }); } } },
进行要旋转动画的附加,将这些要旋转的小立方体附加完动画后则放到动画队列里然后延时调用timerFun方法从队列中取出对象应用动画,此时未到动画播放完成前,对于动画播放过程进行加锁控制,防止动画的重复执行,产生混乱状况。
第五步:如何保持旋指定面的旋转状态,实现魔方旋转特效
这一步我认为是最为重要的一步,比如先绕Z轴索引为3的面也就是魔方的前面(front)正向顺时针旋转90度后,再绕X轴索引为1的面(left)正向顺时针旋转90后应该是这个样子
如何实现这种效果的?正常的逻辑是什么?是不是魔方前面的所有立方体按Z轴顺时针旋转90后,再获取魔方左面上的立方体按X轴顺时针旋转90呢?而现实却不是这样的,这样的逻辑会有两个问题;
第一个问题是:当动画播放完成后则会恢复对象播放之前的状态,对象都还是在原来的位置,这时候或许有人回想,那么既然播放完成后会恢复到原来的状态,那么在完成事件里则再按照动画旋转的方向和角度,实际旋转对应的角度和方向,那么对象不都到了旋转后的位置上了吗,对,这样想是可以的,但是当再用getXFace这方法来获取索引为1(魔方左面)上的立方体时获取其实还是左面(left)颜色都为绿色的立方体,而不包含现在新旋转过来的粉红色的立方体,所以旋转后根本不是右边图的样子。所以需要想用getXFace这个方法获取到6个绿色的立方体及3个新旋转过来的粉红色的立方体,则需要这3个粉红色的立方体跟原来位置的绿色的立方体换一下即(18,19,20跟18,21,24),这个逻辑好像也是对的,这便是第二个问题,但是换之后会形成什么样子呢?那就是再进行绕X轴索引为1的面(left)正向顺时针旋转90时就不在是绕着X轴旋转了,其实就是从上面(top)的Z轴索引为3的立方体旋转到X轴索引为1的下面立方体(bottom)即(18->6,19->21,20->24),所以两种位置的状态的动画则不再是魔方按某个面旋转的效果。所以最终的办法就是在动画结束事件里换面的颜色,代码如下:
animationEnd: function () { var offset = this.angle / 90, faceCubes = [], otherFace; var zSideFace = ["top", "left", "bottom", "right"], xSideFace = ["back", "top", "front", "bottom"], ySideFace = ["back", "left", "front", "right"], sideFace = []; for (var i = 0, len = this.cubeRotateStatus.length; i < len; i++) { var status = this.cubeRotateStatus[i]; if (status) { var dimensionIndex = i + 1; switch (this.rotateFace) { case "rotateX": faceCubes = this.getXFace(dimensionIndex); sideFace = xSideFace; if (dimensionIndex == 1) otherFace = "left"; else if (dimensionIndex == this.dimension) otherFace = "right"; break; case "rotateY": faceCubes = this.getYFace(dimensionIndex); sideFace = ySideFace; if (dimensionIndex == 1) otherFace = "top"; else if (dimensionIndex == this.dimension) otherFace = "bottom"; break; case "rotateZ": faceCubes = this.getZFace(dimensionIndex); sideFace = zSideFace; if (dimensionIndex == 1) otherFace = "back"; else if (dimensionIndex == this.dimension) otherFace = "front"; break; } this.setRotateDirectSideBackGround(faceCubes, sideFace, offset, status); if (dimensionIndex == 1 || dimensionIndex == this.dimension) this.setRotateOtherDirectSideBackGround(faceCubes, otherFace, offset, status); } } // console.info(this.rollMoveStack.length + "," + this.moveDirect); if (this.autoPlay) { if (this.moveDirect) this.moveMagicCube(); else this.moveRollBackCube(); } else { this.lock = false; } // alert("运行结束"); }
这个方法里有两个很重要的方法一个是
setRotateDirectSideBackGround: function (faceCubes, sideFace, offset, status) { var oldSides = this.getSideCubes(faceCubes, 0), backColor = []; if (oldSides == null) return; var offsetNIndex, offsetPIndex; for (var j = 0; j < 4; j++) { offsetPIndex = (j - offset + 4) % 4; offsetNIndex = (j + offset) % 4; var offsetIndex; if (this.rotateFace == "rotateY") { offsetIndex = status == 1 ? offsetPIndex : offsetNIndex; } else { offsetIndex = status == 2 ? offsetPIndex : offsetNIndex; } backColor[j] = this.getSideBackGround(oldSides[offsetIndex], sideFace[offsetIndex]); } for (var j = 0; j < 4; j++) { for (var k = 0; k < oldSides[j].length; k++) { oldSides[j][k][sideFace[j]].style.background = backColor[j][k]; } } }
他的作用是18->24,19->21,20->18;20->18,23->19,26->20;26->20,25->23,24->20;24->26,21->25->18->24 即用->前面标号的颜色代替后面标号的颜色,如上图中浅绿色前头标识,这里替换的方向是顺着旋转方向的面。
另一个是
setRotateOtherDirectSideBackGround: function (faceCubes, otherFace, offset, status) { var oldSides = [], backColor = []; var offsetNIndex, offsetPIndex; for (var i = 0; i <= parseInt(this.dimension / 2) - 1; i++) { oldSides = this.getSideCubes(faceCubes, i), backColor = []; for (var j = 0; j < 4; j++) { offsetPIndex = (j - offset + 4) % 4; offsetNIndex = (j + offset) % 4; var offsetIndex; if (this.rotateFace == "rotateY") { offsetIndex = status == 1 ? offsetPIndex : offsetNIndex; } else { offsetIndex = status == 2 ? offsetPIndex : offsetNIndex; } backColor[j] = this.getSideBackGround(oldSides[offsetIndex], otherFace); } var hasManaged = []; for (var j = 0; j < 4; j++) { for (var k = 0; k < oldSides[j].length; k++) { if (hasManaged.indexOf(oldSides[j][k].index) < 0) { oldSides[j][k][otherFace].style.background = backColor[j][k]; hasManaged.push(oldSides[j][k].index); } } } } }
他的作用是 18->21,21->24;24->25,25->26;26->23,23->20;20->19,19->18;也是用->前面标号的颜色代替后面标号的颜色, 如上图中白色前头标识,不过这里是对应旋转方向两侧的面。
而实现这两个方法的基础则是
getSideCubes: function (cubes, circleIndex) {//获取魔方某个面上的从外围开始的指定圈上的立方体,分成上下左右四个数组,上右倒叙,使整个圈上按一个顺序排列 var sides = [], top = [], left = [], bottom = [], right = []; if (circleIndex < 0 || circleIndex > this.dimension / 2 - 1) return null; for (var i = 0, count = this.dimension - circleIndex * 2; i < count; i++) { top.push(cubes[circleIndex * this.dimension + circleIndex + i]); left.push(cubes[circleIndex * this.dimension + circleIndex + i * this.dimension]); bottom.push(cubes[(this.dimension - 1 - circleIndex) * this.dimension + circleIndex + i]); right.push(cubes[circleIndex * this.dimension + circleIndex + i * this.dimension + (this.dimension - (circleIndex * 2) - 1)]); } sides.push(this.orderByDesc(top)); sides.push(left); sides.push(bottom); sides.push(this.orderByDesc(right)); return sides; }
两个参数cubes:指的是某个面上的所有小立方体,circleIndex指的是从外到内的第几圈对应的所有立方体,并且全部按照顺时针排序。
好了整个原理到此已经差不多了,具体其他细节就看代码吧,完整代码如下:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 5 <title></title> 6 <meta charset="utf-8" /> 7 <meta name="viewport" content="target-densitydpi=320,width=640,user-scalable=no"> 8 <script language="javascript" type="text/javascript"> 9 var cache = {}; 10 (function (exports) { 11 //手机滑动事件对象 12 function touchEvent(target, startEvent, moveEvent, endEvent) { 13 var slider = { 14 //判断设备是否支持touch事件 15 touch: ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch, 16 slider: null, 17 startEvent: null, 18 moveEvent: null, 19 endEvent: null, 20 //滑动开始 21 start: function (event) { 22 var touch = event.targetTouches[0]; //touches数组对象获得屏幕上所有的touch,取第一个touch 23 endPos = startPos = { x: touch.pageX, y: touch.pageY, time: +new Date }; //取第一个touch的坐标值 24 isScrolling = 0; //这个参数判断是垂直滚动还是水平滚动 25 slider.slider.addEventListener('touchmove', slider.move, false); 26 slider.slider.addEventListener('touchend', slider.end, false); 27 if (slider.startEvent != undefined && typeof (slider.startEvent) == "function") 28 slider.startEvent.call(this, touch); 29 }, 30 //移动 31 move: function (event) { 32 //当屏幕有多个touch或者页面被缩放过,就不执行move操作 33 if (event.targetTouches.length > 1 || event.scale && event.scale !== 1) return; 34 var touch = event.targetTouches[0]; 35 endPos = { x: touch.pageX - startPos.x, y: touch.pageY - startPos.y }; 36 isScrolling = Math.abs(endPos.x) < Math.abs(endPos.y) ? 1 : 0; //isScrolling为1时,表示纵向滑动,0为横向滑动 37 if (isScrolling === 0) { 38 // event.preventDefault(); //阻止触摸事件的默认行为,即阻止滚屏 39 } 40 if (slider.moveEvent != undefined && typeof (slider.moveEvent) == "function") 41 slider.moveEvent.call(this, touch); 42 }, 43 //滑动释放 44 end: function (event) { 45 var duration = +new Date - startPos.time; //滑动的持续时间 46 if (isScrolling === 0) { //当为水平滚动时 47 if (Number(duration) > 10) { 48 //判断是左移还是右移,当偏移量大于10时执行 49 if (endPos.x > 10) { 50 51 } else if (endPos.x < -10) { 52 53 } 54 } 55 } 56 if (slider.endEvent != undefined && typeof (slider.endEvent) == "function") 57 slider.endEvent.call(this, event); 58 //解绑事件 59 slider.slider.removeEventListener('touchmove', slider.move, false); 60 slider.slider.removeEventListener('touchend', slider.end, false); 61 } 62 , 63 64 //初始化 65 init: function () { 66 var self = this; //this指slider对象 67 if (!!self.touch) self.slider.addEventListener('touchstart', this.start, false); //addEventListener第二个参数可以传一个对象,会调用该对象的handleEvent属性 68 } 69 } 70 slider.slider = target; 71 slider.startEvent = startEvent; 72 slider.moveEvent = moveEvent; 73 slider.endEvent = endEvent; 74 slider.init(); 75 return { touch: slider.touch, target: slider.slider } 76 }; 77 //windows按下拖动对象 78 function dragEvent(down_target, move_target, down, move, up) { 79 var drag = { 80 down_target: null, 81 move_target: null, 82 move: null, 83 up: null, 84 down: null, 85 intial: function (down_target, move_target, down, move, up) { 86 if (down_target == undefined || move_target == undefined || move == undefined) 87 return; 88 this.down_target = down_target; 89 this.move_target = move_target; 90 this.down = down; 91 this.move = move; 92 this.up = up; 93 drag.prefixedEvent(drag.down_target, "mousedown", this.mousedown, true); 94 drag.prefixedEvent(drag.down_target, "mouseup", this.mouseup, true); 95 drag.prefixedEvent(document, "mouseup", this.mouseup, true); 96 }, 97 mouseup: function (e) { 98 drag.stopEvent(e); //这里需要冒泡 99 if (drag.up != undefined && typeof (drag.up) == "function") 100 drag.up.call(this, e); 101 drag.prefixedEvent(drag.move_target, "mousemove", drag.mousemove, false); 102 }, 103 mousemove: function (evt) { 104 var e = (evt) ? evt : window.event; 105 drag.move.call(this, e); 106 }, 107 mousedown: function (evt) { 108 drag.stopEvent(evt); //这里阻止冒泡 109 var e = (evt) ? evt : window.event; 110 if (drag.down != undefined && typeof (drag.down) == "function") 111 drag.down.call(this, e); 112 drag.prefixedEvent(drag.move_target, "mousemove", drag.mousemove, true); 113 }, 114 stopEvent: function (evt) { 115 if (evt == undefined) return; 116 var e = (evt) ? evt : window.event; //判断浏览器的类型,在基于ie内核的浏览器中的使用cancelBubble 117 if (window.event) { e.cancelBubble = true; } else { e.stopPropagation(); } 118 }, 119 prefixedEvent: function (element, type, callback, isAdd) { 120 var pfx = ["webkit", "moz", "MS", "o", ""]; 121 for (var p = 0; p < pfx.length; p++) { 122 if (!pfx[p]) type = type.toLowerCase(); 123 if (isAdd) 124 element.addEventListener(pfx[p] + type, callback, false); 125 else 126 element.removeEventListener(pfx[p] + type, callback, false); 127 } 128 } 129 } 130 drag.intial(down_target, move_target, down, move, up); 131 return drag; 132 } 133 134 //手动旋转控制器面板 135 function Controller(opts) { 136 opts = opts || {}; 137 this.parent = opts.parent; //插入到哪里 138 this.pWidth = this.parent.clientWidth; 139 this.pHeight = this.parent.clientHeight; 140 this.width = this.pWidth / 3; 141 this.height = this.pHeight / 3; 142 this.upImg = opts.upImg || 'http://cdn.attach.w3cfuns.com/notes/pics/201605/30/104921ksxt7eexn5ox17oa.png'; 143 this.downImg = opts.upImg || 'http://cdn.attach.w3cfuns.com/notes/pics/201605/30/104921hh9accx2ymfc42xb.png'; 144 this.leftImg = opts.upImg || 'http://cdn.attach.w3cfuns.com/notes/pics/201605/30/104921wufxpjffbplehvll.png'; 145 this.rightImg = opts.upImg || 'http://cdn.attach.w3cfuns.com/notes/pics/201605/30/104921mpvrvnjoxirj8ov7.png'; 146 this.Click = opts.Click || null; 147 this.init(); 148 } 149 Controller.prototype = { 150 init: function () { 151 this.createUp(); 152 this.createDown(); 153 this.createLeft(); 154 this.createRight(); 155 }, 156 createDiv: function () { 157 var div = document.createElement('div'); 158 div.style.margin = 0; 159 div.style.position = "absolute"; 160 div.style.width = this.width + "px"; 161 div.style.height = this.height + "px"; 162 //div.style.backgroundRepeat = "no-repeat"; 163 //div.style.backgroundPosition = "center"; 164 //div.style.backgroundAttachment = "fixed"; 165 div.style.backgroundSize = "100% 100%"; 166 div.style.cursor = "pointer"; 167 var that = this; 168 div.addEventListener("mouseup", this.Click, false); 169 return div; 170 }, 171 createUp: function () { 172 var div = this.createDiv(); 173 div.style.backgroundImage = "url(" + this.upImg + ")"; 174 div.style.left = "33%"; 175 div.style.top = "10%"; 176 div.direct = "up"; 177 this.up = div; 178 this.parent.appendChild(div); 179 }, 180 createDown: function () { 181 var div = this.createDiv(); 182 div.style.backgroundImage = "url(" + this.downImg + ")"; 183 div.style.left = "33%"; 184 div.style.top = "56%"; 185 div.direct = "down"; 186 this.down = div; 187 this.parent.appendChild(div); 188 }, 189 createLeft: function () { 190 var div = this.createDiv(); 191 div.style.backgroundImage = "url(" + this.leftImg + ")"; 192 div.style.left = "10%"; 193 div.style.top = "33%"; 194 div.direct = "left"; 195 this.left = div; 196 this.parent.appendChild(div); 197 }, 198 createRight: function () { 199 var div = this.createDiv(); 200 div.style.backgroundImage = "url(" + this.rightImg + ")"; 201 div.style.left = "56%"; 202 div.style.top = "33%"; 203 div.direct = "right"; 204 this.right = div; 205 this.parent.appendChild(div); 206 } 207 } 208 //单个立方体对象 209 function Cube(opts) { 210 opts = opts || {}; 211 this.parent = opts.parent; //插入到哪里 212 this.browserPrefix = opts.browserPrefix; 213 this.width = opts.width; 214 this.height = opts.height; 215 this.cubZ = opts.cubZ; 216 this.face = opts.face; 217 this.row = opts.row; 218 this.column = opts.column; 219 this.index=opts.index; 220 this.offsetX = this.column * (this.width + opts.marginX); // 221 this.offsetY = this.row * (this.height + opts.marginY); // 222 this.offsetZ = this.face * (this.cubZ + opts.marginZ); // 223 this.positiveZ = this.cubZ / 2; 224 this.negativeZ = -this.cubZ / 2; 225 this.cubFaceInfo = opts.cubFaceInfo; 226 this.dimension = opts.dimension; 227 this.centerX = (this.dimension * this.width + (this.dimension - 1) * opts.marginX) / 2; 228 this.centerY = (this.dimension * this.height + (this.dimension - 1) * opts.marginY) / 2; 229 this.centerZ = (this.dimension * this.cubZ + (this.dimension - 1) * opts.marginZ) / 2; 230 this.translateX = this.offsetX - this.centerX; //把中心点设为原点 231 this.translateY = this.offsetY - this.centerY; // 232 this.translateZ = this.cubZ / 2 + this.offsetZ - this.centerZ; //offsetZ按上面计算应该跟x,y在一个平面上即后面,但实际上由于要形成立方体,在Z轴上已经后退了cubZ/2个距离,故为和上面保持一致在一个面上,这里需要再加回cubZ/2个距离,使默认的xyz原点都在一个平面上即立方体后面左上角三维坐标系,以这个点作为参考点平移和设置旋转原点 233 this.cubeFace = []; 234 this.rotateTransfrom = ""; 235 this.init(); 236 237 } 238 Cube.prototype = { 239 init: function () { 240 this.createCubeBox(); 241 this.createFront(); 242 this.createBack(); 243 this.createTop(); 244 this.createBottom(); 245 this.createLeft(); 246 this.createRight(); 247 248 }, 249 createCubeBox: function () { 250 this.Box = document.createElement('div'); 251 this.Box.style.width = this.width + "px"; 252 this.Box.style.height = this.height + "px"; 253 this.Box.style.left = "50%"; 254 this.Box.style.top = "50%"; 255 this.Box.style.position = "absolute"; 256 this.Box.style[this.browserPrefix + "TransformStyle"] = "preserve-3d"; 257 this.Box.style[this.browserPrefix + "Perspective"] = "0"; 258 // this.Scene.style[this.browserPrefix + "backfaceVisibility"] = "hidden"; 259 this.intalTransform = "translateZ(" + this.translateZ + "px) translateX(" + this.translateX + "px) translateY(" + this.translateY + "px)"; 260 this.Box.style[this.browserPrefix + "Transform"] = this.intalTransform; 261 this.Box.style[this.browserPrefix + "TransformOrigin"] = "" + (-this.translateX) + "px " + (-this.translateY) + "px " + (-this.translateZ) + "px"; 262 this.Box.index=this.index; 263 this.parent.appendChild(this.Box); 264 this.x = window.getComputedStyle(this.Box).getPropertyValue('left'); 265 this.y = window.getComputedStyle(this.Box).getPropertyValue('top'); 266 this.matrix3d = window.getComputedStyle(this.Box).getPropertyValue('transform'); 267 }, 268 createFace: function () { 269 var face = document.createElement('div'); 270 face.style.margin = 0; 271 face.style.position = "absolute"; 272 face.style.width = this.width + "px"; 273 face.style.height = this.height + "px"; 274 face.style.textAlign="center"; 275 face.style.lineHeight=this.height + "px"; 276 // face.innerHTML=this.index; 277 return face; 278 }, 279 createFront: function () { 280 var face = this.createFace(); 281 face.style[this.browserPrefix + "Transform"] = "translateZ(" + this.positiveZ + "px) "; 282 face.translateZ = this.positiveZ; //给生成控制面板用 283 this.cubeFace.push(face); 284 this.front = face; 285 this.Box.appendChild(face); 286 }, 287 createBack: function () { 288 var face = this.createFace(); 289 face.style[this.browserPrefix + "Transform"] = "translateZ(" + this.negativeZ + "px) "; 290 face.translateZ = this.negativeZ; //给生成控制面板用 291 this.cubeFace.push(face); 292 this.back = face; 293 this.Box.appendChild(face); 294 }, 295 createTop: function () { 296 var face = this.createFace(); 297 face.style[this.browserPrefix + "Transform"] = "rotateX(90deg) translateZ(" + this.positiveZ + "px) "; 298 face.translateZ = this.positiveZ; //给生成控制面板用 299 this.cubeFace.push(face); 300 this.top = face; 301 this.Box.appendChild(face); 302 }, 303 createBottom: function () { 304 var face = this.createFace(); 305 face.style[this.browserPrefix + "Transform"] = "rotateX(90deg) translateZ(" + this.negativeZ + "px) "; 306 face.translateZ = this.negativeZ; //给生成控制面板用 307 this.cubeFace.push(face); 308 this.bottom = face; 309 this.Box.appendChild(face); 310 }, 311 createLeft: function () { 312 var face = this.createFace(); 313 face.style[this.browserPrefix + "Transform"] = "rotateY(90deg) translateZ(" + this.negativeZ + "px) "; 314 face.translateZ = this.negativeZ; //给生成控制面板用 315 this.cubeFace.push(face); 316 this.left = face; 317 this.Box.appendChild(face); 318 }, 319 createRight: function () { 320 var face = this.createFace(); 321 face.style[this.browserPrefix + "Transform"] = "rotateY(90deg) translateZ(" + (this.positiveZ) + "px) "; 322 face.translateZ = this.positiveZ; //给生成控制面板用 323 this.cubeFace.push(face); 324 this.right = face; 325 this.Box.appendChild(face); 326 } 327 328 } 329 exports.magicCube = function (opts) { 330 opts = opts || {}; 331 //外部传入参数 332 this.parent = opts.parent || document.getElementsByTagName('body')[0]; 333 this.dimension = opts.dimension || 3; //魔方级数 334 this.cubWidth = opts.cubWidth || 50; //单个立方体宽度 335 this.cubHeight = opts.cubHeight || 50; //单个立方体高度 336 this.marginLeft = opts.marginLeft || 0; //水平方向间距 337 this.marginTop = opts.marginLeft || 0; //上下方向间距 338 this.marginZ = opts.marginZ || 0; //前后方向间距 339 this.cubZ = opts.cubZ || 50; //单个立方体Z轴距离 340 this.sceneWidth = opts.sceneWidth || (opts.parent != null ? opts.parent.clientWidth : null); //3d场景宽度 341 this.sceneHeight = opts.sceneHeight || (opts.parent != null ? opts.parent.clientHeight : null); //3d场景高度 342 this.Perspective = opts.Perspective || 0; //投影值 343 this.opacity = opts.opacity || 1; 344 this.cubFaceInfo = opts.cubFaceInfo || { front: { backGround: "rgba(255,0,0," + this.opacity + ")" }, back: { backGround: "rgba(255,255,0," + this.opacity + ")" }, left: { backGround: "rgba(0,255,0," + this.opacity + ")" }, right: { backGround: "rgba(0,0,255," + this.opacity + ")" }, top: { backGround: "rgba(255,0,255," + this.opacity + ")" }, bottom: { backGround: "rgba(255,255,255," + this.opacity + ")" }, inner: { backGround: "rgba(150,150,150," + this.opacity + ")" } }; //立方体面信息 345 this.angle = opts.angle || 90; //转动的角度 346 this.rollbackPoint = opts.rollbackPoint || 10; //回滚的步数(自动播放时指定此步数可回归) 347 this.autoPlay = opts.autoPlay || false; //是否自动播放 348 this.delay = opts.delay || 1; //动画延时 349 this.isRandomConfuse = opts.isRandomConfuse || false;//是否自动旋转时是随机打乱模式是,到达指定回归点后便停止自动播放 350 this.statusCallBack = opts.statusCallBack || null;//外部状态回调函数用于显示步数 351 //内部使用参数 352 this.faceCount = this.dimension * this.dimension; //每面立方体个数 353 this.count = this.dimension * this.dimension * this.dimension; //立方体总个数 354 this.cubes = []; //记录组成魔方的单个立方体对象 355 this.controllers = []; //记录控制面板对象 356 this.browserPrefix = ""; 357 this.isRunning = 0; 358 this.timer = null; 359 this.rotateFace; //转动的3维坐标系方向 360 this.moveDirect = true; //正向随机动作还是回归,默认为正向 361 this.cubeMoveQueue = []; 362 this.rollMoveStack = []; //动作回归的堆栈 363 this.lock = false; //旋转事件锁 364 this.scenceRotateXed = 30; 365 this.scenceRotateYed = -30; 366 this.init(); 367 }; 368 magicCube.prototype = { 369 init: function () { 370 this.start(); 371 }, 372 create3dScene: function () { 373 this.Scene = document.createElement('div'); 374 //this.Scene.className = "cube"; 375 var width = this.sceneWidth || this.clientWidth, 376 height = this.sceneHeight || this.clientHeight; 377 this.Scene.style.width = width + "px"; 378 this.Scene.style.height = height + "px"; 379 this.Scene.style.position = "relative"; 380 this.Scene.style.top = "0"; 381 this.Scene.style.left = "0"; 382 this.Scene.style[this.browserPrefix + "TransformStyle"] = "preserve-3d"; 383 this.Scene.style[this.browserPrefix + "Perspective"] = this.Perspective + "px"; 384 // this.Scene.style[this.browserPrefix + "backfaceVisibility"] = "hidden"; 385 this.Scene.style[this.browserPrefix + "Transform"] = "rotateX(" + this.scenceRotateYed + "deg) rotateY(" + this.scenceRotateXed + "deg) "; 386 var that = this; 387 function down(e) { 388 document.downX = e.pageX; 389 document.downY = e.pageY; 390 document.rotateX = that.scenceRotateXed; 391 document.rotateY = that.scenceRotateYed; 392 } 393 function move(e) { 394 var rotateX = e.pageX - document.downX + that.scenceRotateXed, 395 rotateY = document.downY - e.pageY + that.scenceRotateYed; 396 // console.log(rotateX + "," + rotateY); 397 that.Scene.style[that.browserPrefix + "Transform"] = "rotateX(" + rotateY + "deg) rotateY(" + rotateX + "deg) "; 398 document.rotateX = rotateX; 399 document.rotateY = rotateY; 400 } 401 function up(e) { 402 if (this == that.parent) { 403 that.scenceRotateXed = document.rotateX; 404 that.scenceRotateYed = document.rotateY; 405 } 406 } 407 dragEvent(this.parent, this.parent, down, move, up); 408 touchEvent(this.parent, down, move, up); 409 this.parent.appendChild(this.Scene); 410 411 }, 412 create: function (face, row, column,index) { 413 return new Cube({ 414 parent: this.Scene, 415 dimension: this.dimension, 416 width: this.cubWidth, 417 height: this.cubHeight, 418 cubZ: this.cubZ, 419 face: face, 420 row: row, 421 column: column, 422 index:index, 423 browserPrefix: this.browserPrefix, 424 cubFaceInfo: this.cubFaceInfo, 425 marginX: this.marginLeft, 426 marginY: this.marginTop, 427 marginZ: this.marginZ, 428 dimension: this.dimension 429 }); 430 }, 431 createMagicCube: function (index) { 432 var face = 0, row = 0, column = 0; 433 for (var i = 0; i < this.count; i++) { 434 this.cubes.push(this.create(face, row, column,i)); 435 column++; 436 if ((i + 1) % this.dimension === 0) { 437 row++; 438 column = 0; 439 } 440 if ((i + 1) % this.faceCount === 0) { 441 face++; 442 row = 0; 443 } 444 } 445 }, 446 drawBackGroundColor: function () { 447 for (var face in this.cubFaceInfo) { 448 if (face == "inner") { 449 this.setInnerBKColor(this.cubFaceInfo[face].backGround); 450 } 451 else { 452 var cube = this.getCubesByFace(face); 453 for (var i = 0, len = cube.length; i < len; i++) { 454 var controlPanel = this.getControlPanel(cube[i].Box, cube[i][face]); 455 this.createController(controlPanel, face, cube[i].index); //创建对应的控制面板 456 cube[i][face].style.background = this.cubFaceInfo[face].backGround; 457 this.bindMouseEvent(cube[i][face], face); //绑定触发和解除控制面板的事件 458 } 459 } 460 } 461 462 }, 463 bindMouseEvent: function (faceElement, face) { 464 faceElement.face = face; 465 faceElement.addEventListener("mouseover", function (e) { _this = cache.magicCube; if (_this.lock || _this.autoPlay) return; _this.showController(this.parentNode.index, this.face); _this.stopEvent(e); }, false); 466 faceElement.addEventListener("mouseout", function (e) { _this = cache.magicCube; if (_this.lock || _this.autoPlay) return; _this.hideController(this.parentNode.index, this.face); _this.stopEvent(e); }, false); 467 }, 468 createController: function (controlPanel, face, index) { 469 controlPanel.style.visibility = "hidden"; 470 controlPanel.style.background = "rgba(255,255,255,.3)"; 471 controlPanel.isFocus = false; 472 controlPanel.addEventListener("mouseover", function (e) { this.isFocus = true; }, false); 473 controlPanel.addEventListener("mouseout", function (e) { this.isFocus = false; _this = cache.magicCube; _this.hideController(this.index, this.face); }, false); 474 controlPanel.index = index; //记录索引 点击事件去查找对应的立方体 475 controlPanel.face = face; 476 var controller = new Controller({ parent: controlPanel, Click: this.RoateEvent }); //产生控制面板 477 controller.index = index; 478 controller.face = face; 479 this.controllers.push(controller); 480 }, 481 showController: function (index, face) { 482 var controller = this.getControllerByIndex(index, face); 483 controller.style.visibility = "visible"; 484 }, 485 hideController: function (index, face, async) { 486 var controller = this.getControllerByIndex(index, face); 487 setTimeout(function () { 488 if (!controller.isFocus) 489 controller.style.visibility = "hidden"; 490 }, 100); 491 492 }, 493 getControllerByIndex: function (index, face) { 494 for (var i = 0, len = this.controllers.length; i < len; i++) { 495 if (this.controllers[i].index == index && this.controllers[i].face == face) 496 return this.controllers[i].parent; 497 } 498 }, 499 stopEvent: function (evt) { 500 var e = (evt) ? evt : window.event; //判断浏览器的类型,在基于ie内核的浏览器中的使用cancelBubble 501 if (window.event) { e.cancelBubble = true; } else { e.stopPropagation(); } 502 }, 503 getControlPanel: function (parent, source) //克隆出一个面作为控制面板 504 { 505 var cNode = source.cloneNode(true); 506 var translateZ = parseInt(source.translateZ); 507 translateZ = translateZ > 0 ? 5 : -5; 508 cNode.style[this.browserPrefix + "Transform"] += "translateZ(" + translateZ + "px) "; 509 //cNode.style[this.browserPrefix + "BorderRadius"]=source.translateZ+"px"; 510 parent.appendChild(cNode); 511 return cNode; 512 }, 513 setInnerBKColor: function (color) { 514 for (var i = 0; i < this.count; i++) { 515 for (var j = 0; j < 6; j++) { 516 if (this.cubes[i].cubeFace[j].style.background == "") { 517 this.cubes[i].cubeFace[j].style.background = color; 518 } 519 } 520 } 521 }, 522 getZFace: function (zIndex) { 523 var zFace = []; 524 if (zIndex < 1 || zIndex > this.dimension) 525 return null; 526 for (var i = (zIndex - 1) * this.faceCount; i < zIndex * this.faceCount; i++) { 527 zFace.push(this.cubes[i]); 528 } 529 return zFace; 530 }, 531 getXFace: function (xIndex) { 532 var xFace = []; 533 if (xIndex < 1 || xIndex > this.dimension) 534 return null; 535 for (var i = 0; i < this.count; i++) { 536 if (i % this.dimension == 0) 537 xFace.push(this.cubes[i + xIndex - 1]); 538 } 539 return xFace; 540 }, 541 getYFace: function (yIndex) { 542 var yFace = []; 543 if (yIndex < 1 || yIndex > this.dimension) 544 return null; 545 for (var i = 0; i < this.count; i++) { 546 if (i % this.faceCount == (yIndex - 1) * this.dimension) { 547 for (var j = 0; j < this.dimension; j++) 548 yFace.push(this.cubes[i + j]); 549 } 550 } 551 return yFace; 552 }, 553 getSideCubes: function (cubes, circleIndex) {//获取魔方某个面上的从外围开始的指定圈上的立方体,分成上下左右四个数组,上右倒叙,使整个圈上按一个顺序排列 554 var sides = [], top = [], left = [], bottom = [], right = []; 555 if (circleIndex < 0 || circleIndex > this.dimension / 2 - 1) 556 return null; 557 for (var i = 0, count = this.dimension - circleIndex * 2; i < count; i++) { 558 top.push(cubes[circleIndex * this.dimension + circleIndex + i]); 559 left.push(cubes[circleIndex * this.dimension + circleIndex + i * this.dimension]); 560 bottom.push(cubes[(this.dimension - 1 - circleIndex) * this.dimension + circleIndex + i]); 561 right.push(cubes[circleIndex * this.dimension + circleIndex + i * this.dimension + (this.dimension - (circleIndex * 2) - 1)]); 562 } 563 sides.push(this.orderByDesc(top)); 564 sides.push(left); 565 sides.push(bottom); 566 sides.push(this.orderByDesc(right)); 567 return sides; 568 }, 569 getCubesByFace: function (face) { 570 switch (face) { 571 case "front": return this.getZFace(this.dimension); 572 case "back": return this.getZFace(1); 573 case "left": return this.getXFace(1); 574 case "right": return this.getXFace(this.dimension); 575 case "top": return this.getYFace(1); 576 case "bottom": return this.getYFace(this.dimension); 577 } 578 }, 579 moveMagicCube: function () { 580 if (this.cubes.length < 1) return; 581 //var cubes = this.getYFace(2); 582 //for (var i = 0, len = cubes.length; i < len; i++) { 583 // cubes[i].Box.className = "rotate"; 584 //} 585 //随机产生3D转动方向 586 this.isRunning = 0; 587 var direct = this.random(1, 3), rotateDirect = "", getFaceFun; 588 // direct=3; 589 switch (direct) { 590 case 1: rotateDirect = "rotateX"; getFaceFun = this.getXFace; break; 591 case 2: rotateDirect = "rotateY"; getFaceFun = this.getYFace; break; 592 case 3: rotateDirect = "rotateZ"; getFaceFun = this.getZFace; break; 593 } 594 this.rotateFace = rotateDirect; 595 this.cubeRotateStatus = []; 596 for (var i = 1; i <= this.dimension; i++) { 597 var status = this.random(0, 2); 598 this.cubeRotateStatus.push(status); 599 switch (status) { 600 case 0: break; //不转动 601 case 1: this.rotateBox(this.angle, rotateDirect, i, getFaceFun.call(this, i)); break; //正向转动90 602 case 2: this.rotateBox(-this.angle, rotateDirect, i, getFaceFun.call(this, i)); break; //反向转动90 603 } 604 605 } 606 var flag = false; 607 for (var i = 0, len = this.cubeRotateStatus.length; i < len; i++) { 608 if (this.cubeRotateStatus[i]) { 609 flag = true; 610 break; 611 } 612 } 613 if (!flag) {//一个都没转的情况 则强制补充一个 614 var index = this.random(1, this.dimension); 615 this.rotateBox(this.angle, rotateDirect, index, getFaceFun.call(this, index)); //正向转动90 616 this.cubeRotateStatus[index - 1] = 1; //全都不转动 默认选出一个 使其正向转动指定度数 617 } 618 setTimeout(this.timerFun, 100); 619 this.rollMoveStack.push({ rotateFace: this.rotateFace, cubeRotateStatus: this.cubeRotateStatus }); //记录动作状态 620 if (this.statusCallBack != null && typeof (this.statusCallBack) == "function") 621 this.statusCallBack(this.rollMoveStack.length); 622 if (this.rollMoveStack.length == this.rollbackPoint)//判断当达到阀值时切换动作方向为回归 623 { 624 if (this.isRandomConfuse) 625 this.autoPlay = false;//如果是随机打乱模式则终止 626 else 627 this.moveDirect = false;//否则反方向回归 628 } 629 630 }, 631 getXYByFaceCubes: function (faceCubes, index) {//获取当前当前索引对应的立方体所在面的行X列Y坐标 632 var y = 1, x = 1; 633 for (var i = 1, len = faceCubes.length; i <= len; i++) { 634 if (faceCubes[i - 1].index == index) 635 return { Y: y, X: x }; 636 x++; 637 if (i % this.dimension == 0) { 638 y++; 639 x = 1; 640 } 641 642 } 643 }, 644 RoateEvent: function (e) { 645 var _this = cache.magicCube, positon, rotateTarget = {}; 646 //隐藏控制面板 647 //this.parentNode.isFocus = false; 648 //_this.hideController(this.parentNode.index, this.parentNode.face); 649 //开始定位立方体 650 var cubes = _this.getCubesByFace(this.parentNode.face); 651 switch (this.parentNode.face) { 652 case "back": 653 case "front": positon = _this.getXYByFaceCubes(cubes, this.parentNode.index); rotateTarget.moveY = { rotateDirect: "rotateX", getFaceFun: _this.getXFace, index: positon.X }; rotateTarget.moveX = { rotateDirect: "rotateY", getFaceFun: _this.getYFace, index: positon.Y }; break; 654 case "top": 655 case "bottom": positon = _this.getXYByFaceCubes(cubes, this.parentNode.index); rotateTarget.moveY = { rotateDirect: "rotateX", getFaceFun: _this.getXFace, index: positon.X }; rotateTarget.moveX = { rotateDirect: "rotateZ", getFaceFun: _this.getZFace, index: positon.Y }; break; 656 case "left": 657 case "right": positon = _this.getXYByFaceCubes(cubes, this.parentNode.index); rotateTarget.moveY = { rotateDirect: "rotateZ", getFaceFun: _this.getZFace, index: positon.Y }; rotateTarget.moveX = { rotateDirect: "rotateY", getFaceFun: _this.getYFace, index: positon.X }; break; 658 } 659 //rotateTarget.moveY 表示沿相对于屏幕的纵向Y方向旋转,moveX表示沿相对于屏幕的横向X方向旋转 660 _this.cubeRotateStatus = []; 661 for (var i = 1; i <= _this.dimension; i++) 662 _this.cubeRotateStatus[i - 1] = 0; 663 _this.isRunning = 0; 664 var angle, status; 665 if (this.direct == "left" || this.direct == "right") {//横向旋转 666 var leftOrright = this.parentNode.face == "left" || this.parentNode.face == "right"; //对于绕Y轴旋转的有点特殊跟正常方向相反 667 if (this.direct == "left") { 668 angle = leftOrright ? _this.angle : -_this.angle; 669 status = leftOrright ? 1 : 2; 670 } else { 671 angle = leftOrright ? -_this.angle : _this.angle; 672 status = leftOrright ? 2 : 1; 673 } 674 if (this.parentNode.face == "right" || this.parentNode.face == "back" || this.parentNode.face == "bottom") {//取反 675 angle = -angle; 676 status = 3 - status; 677 } 678 _this.rotateBox(angle, rotateTarget.moveX.rotateDirect, rotateTarget.moveX.index, rotateTarget.moveX.getFaceFun.call(_this, rotateTarget.moveX.index)); 679 _this.rotateFace = rotateTarget.moveX.rotateDirect; 680 _this.cubeRotateStatus[rotateTarget.moveX.index - 1] = status; 681 682 } 683 else if (this.direct == "up" || this.direct == "down") {//纵向旋转 684 if (this.direct == "up") { 685 angle = _this.angle 686 status = 1; 687 } else { 688 angle = -_this.angle; 689 status = 2; 690 } 691 if (this.parentNode.face == "right" || this.parentNode.face == "back" || this.parentNode.face == "bottom") {//取反 692 angle = -angle; 693 status = 3 - status; 694 } 695 _this.rotateBox(angle, rotateTarget.moveY.rotateDirect, rotateTarget.moveY.index, rotateTarget.moveY.getFaceFun.call(_this, rotateTarget.moveY.index)); 696 _this.rotateFace = rotateTarget.moveY.rotateDirect; 697 _this.cubeRotateStatus[rotateTarget.moveY.index - 1] = status; 698 } 699 _this.lock = true; 700 setTimeout(_this.timerFun, 100); 701 _this.rollMoveStack.push({ rotateFace: _this.rotateFace, cubeRotateStatus: _this.cubeRotateStatus }); //记录动作状态 702 if (_this.statusCallBack != null && typeof (_this.statusCallBack) == "function") 703 _this.statusCallBack(_this.rollMoveStack.length); 704 }, 705 moveRollBackCube: function () { 706 var record = this.rollMoveStack.pop(), getFaceFun; 707 this.rotateFace = record.rotateFace; 708 this.isRunning = 0; 709 switch (record.rotateFace) { 710 case "rotateX": getFaceFun = this.getXFace; break; 711 case "rotateY": getFaceFun = this.getYFace; break; 712 case "rotateZ": getFaceFun = this.getZFace; break; 713 } 714 this.cubeRotateStatus = []; 715 for (var i = 0, len = record.cubeRotateStatus.length; i < len; i++) { 716 var dimensionIndex = i + 1, status = record.cubeRotateStatus[i]; 717 if (status == 1) { 718 this.cubeRotateStatus.push(2); //1 变2,2变1 719 this.rotateBox(-this.angle, record.rotateFace, dimensionIndex, getFaceFun.call(this, dimensionIndex)); //反向转动90 720 } 721 else if (status == 2) { 722 this.cubeRotateStatus.push(1); //1 变2,2变1 723 this.rotateBox(this.angle, record.rotateFace, dimensionIndex, getFaceFun.call(this, dimensionIndex)); //反向转动90 724 } 725 else { 726 this.cubeRotateStatus.push(0); 727 } 728 } 729 setTimeout(this.timerFun, 100); 730 if (this.statusCallBack != null && typeof (this.statusCallBack) == "function") 731 this.statusCallBack(this.rollMoveStack.length); 732 if (this.rollMoveStack.length == 0)//判断当达到0时切换动作为正向随机 733 this.moveDirect = true; 734 }, 735 intersect: function (source, target) { 736 var data = []; 737 for (var i = 0, len = source.length; i < len; i++) { 738 var index = target.indexOf(source[i]); 739 if (index >= 0) 740 data.push(source[i]) 741 } 742 return data; 743 }, 744 orderByDesc: function (datas) { 745 var temp; 746 for (var i = 0; i < datas.length - 1; i++) { 747 for (var j = i + 1; j < datas.length; j++) { 748 if (parseFloat(datas[i].index) < parseFloat(datas[j].index)) { 749 temp = datas[i]; 750 datas[i] = datas[j]; 751 datas[j] = temp; 752 } 753 } 754 } 755 return datas; 756 }, 757 getSideBackGround: function (sideFaces, face) { 758 var backGrounds = []; 759 for (var i = 0, len = sideFaces.length; i < len; i++) { 760 backGrounds.push(sideFaces[i][face].style.background); 761 } 762 return backGrounds; 763 }, 764 setRotateDirectSideBackGround: function (faceCubes, sideFace, offset, status) { 765 var oldSides = this.getSideCubes(faceCubes, 0), backColor = []; 766 if (oldSides == null) 767 return; 768 var offsetNIndex, offsetPIndex; 769 for (var j = 0; j < 4; j++) { 770 offsetPIndex = (j - offset + 4) % 4; 771 offsetNIndex = (j + offset) % 4; 772 var offsetIndex; 773 if (this.rotateFace == "rotateY") { 774 offsetIndex = status == 1 ? offsetPIndex : offsetNIndex; 775 } 776 else { 777 offsetIndex = status == 2 ? offsetPIndex : offsetNIndex; 778 } 779 backColor[j] = this.getSideBackGround(oldSides[offsetIndex], sideFace[offsetIndex]); 780 } 781 for (var j = 0; j < 4; j++) { 782 for (var k = 0; k < oldSides[j].length; k++) { 783 oldSides[j][k][sideFace[j]].style.background = backColor[j][k]; 784 } 785 } 786 }, 787 setRotateOtherDirectSideBackGround: function (faceCubes, otherFace, offset, status) { 788 var oldSides = [], backColor = []; 789 var offsetNIndex, offsetPIndex; 790 for (var i = 0; i <= parseInt(this.dimension / 2) - 1; i++) { 791 oldSides = this.getSideCubes(faceCubes, i), backColor = []; 792 for (var j = 0; j < 4; j++) { 793 offsetPIndex = (j - offset + 4) % 4; 794 offsetNIndex = (j + offset) % 4; 795 var offsetIndex; 796 if (this.rotateFace == "rotateY") { 797 offsetIndex = status == 1 ? offsetPIndex : offsetNIndex; 798 } 799 else { 800 offsetIndex = status == 2 ? offsetPIndex : offsetNIndex; 801 } 802 backColor[j] = this.getSideBackGround(oldSides[offsetIndex], otherFace); 803 } 804 var hasManaged = []; 805 for (var j = 0; j < 4; j++) { 806 for (var k = 0; k < oldSides[j].length; k++) { 807 if (hasManaged.indexOf(oldSides[j][k].index) < 0) { 808 oldSides[j][k][otherFace].style.background = backColor[j][k]; 809 hasManaged.push(oldSides[j][k].index); 810 } 811 } 812 } 813 } 814 815 }, 816 animationEnd: function () { 817 var offset = this.angle / 90, faceCubes = [], otherFace; 818 var zSideFace = ["top", "left", "bottom", "right"], xSideFace = ["back", "top", "front", "bottom"], ySideFace = ["back", "left", "front", "right"], sideFace = []; 819 for (var i = 0, len = this.cubeRotateStatus.length; i < len; i++) { 820 var status = this.cubeRotateStatus[i]; 821 if (status) { 822 var dimensionIndex = i + 1; 823 switch (this.rotateFace) { 824 case "rotateX": faceCubes = this.getXFace(dimensionIndex); sideFace = xSideFace; if (dimensionIndex == 1) otherFace = "left"; else if (dimensionIndex == this.dimension) otherFace = "right"; break; 825 case "rotateY": faceCubes = this.getYFace(dimensionIndex); sideFace = ySideFace; if (dimensionIndex == 1) otherFace = "top"; else if (dimensionIndex == this.dimension) otherFace = "bottom"; break; 826 case "rotateZ": faceCubes = this.getZFace(dimensionIndex); sideFace = zSideFace; if (dimensionIndex == 1) otherFace = "back"; else if (dimensionIndex == this.dimension) otherFace = "front"; break; 827 } 828 this.setRotateDirectSideBackGround(faceCubes, sideFace, offset, status); 829 if (dimensionIndex == 1 || dimensionIndex == this.dimension) 830 this.setRotateOtherDirectSideBackGround(faceCubes, otherFace, offset, status); 831 } 832 833 } 834 // console.info(this.rollMoveStack.length + "," + this.moveDirect); 835 if (this.autoPlay) { 836 if (this.moveDirect) 837 this.moveMagicCube(); 838 else 839 this.moveRollBackCube(); 840 } 841 else { 842 this.lock = false; 843 } 844 // alert("运行结束"); 845 }, 846 bindAnimationEvent: function () { 847 var loopMove = function () { 848 cache.magicCube.isRunning--; //由于按组转动,顾要等组成员都完成再进行新的动画 849 if (cache.magicCube.isRunning == 0) 850 cache.magicCube.animationEnd(); 851 } 852 for (var i = 0; i < this.count; i++) { 853 854 this.prefixedEvent(this.cubes[i].Box, "AnimationEnd", loopMove, true); 855 } 856 cache.magicCube = this; //缓存,避免内存泄露 857 }, 858 rotateBox: function (angle, rotateDirect, faceIndex, cubes) { 859 if (cubes != null) { 860 var startStatus = rotateDirect + "(0deg)", endStatus = rotateDirect + "(" + angle + "deg)"; 861 // this.changeAnimationStatus("mydhua", startStatus, endStatus) 862 for (var i = 0, len = cubes.length; i < len; i++) { 863 var ruleName = "roateRule" + faceIndex + i; 864 this.isRunning++; //组成员转动统计 865 //if (cubes[i].rotateTransfrom != "") 866 // startStatus = cubes[i].rotateTransfrom; 867 cubes[i].rotateTransfrom = endStatus; 868 if (this.findKeyframesRule(ruleName) == null) 869 this.createKeyframesRule(ruleName, cubes[i].intalTransform + " " + startStatus, cubes[i].intalTransform + " " + endStatus); 870 else 871 this.changeAnimationStatus(ruleName, cubes[i].intalTransform + " " + startStatus, cubes[i].intalTransform + " " + endStatus); 872 cubes[i].Box.style[this.browserPrefix + "AnimationName"] = "none"; 873 this.cubeMoveQueue.push({ cube: cubes[i], rule: ruleName }); 874 } 875 } 876 }, 877 findKeyframesRule: function (rule) { 878 var ruleName = this.browserPrefix == "" ? "KEYFRAMES_RULE" : this.browserPrefix.toUpperCase() + "_KEYFRAMES_RULE"; 879 var ss = document.styleSheets; 880 for (var i = 0; i < ss.length; ++i) { 881 for (var j = 0; j < ss[i].cssRules.length; ++j) { 882 if (ss[i].cssRules[j].type == window.CSSRule[ruleName] && ss[i].cssRules[j].name == rule) { return ss[i].cssRules[j]; } 883 } 884 } 885 return null; 886 }, 887 createKeyframesRule: function (rule, startStatus, endStatus) { 888 var prefix = this.browserPrefix == "" ? "" : "-" + this.browserPrefix + "-"; 889 var sheet; 890 if (document.styleSheets.length < 1) 891 sheet = this.createSheets(); 892 else 893 sheet = document.styleSheets[0]; 894 var selectorText = "@" + prefix + "keyframes " + rule; 895 var cssText = "0% { " + prefix + "transform: " + startStatus + "; } 100% { " + prefix + "transform: " + endStatus + "; }" 896 if (sheet.insertRule) { 897 sheet.insertRule(selectorText + "{" + cssText + "}", 0); 898 } else if (sheet.addRule) {//兼容IE 899 sheet.addRule(selectorText, cssText, 0); 900 } 901 }, 902 removeKeyframeRule: function (keyframes) { 903 var length = keyframes.cssRules.length; 904 var keyframeString = []; 905 for (var i = 0; i < length; i++) { 906 keyframeString.push(keyframes.cssRules[i].keyText); 907 } 908 //移除动画帧规则 909 for (var i = 0, j = length; i < j; i++) { 910 if (this.browserPrefix == "webkit" || this.browserPrefix == "Moz") 911 keyframes.deleteRule(keyframeString[i]); 912 else 913 keyframes.deleteRule(i); //兼容IE 914 } 915 }, 916 changeAnimationStatus: function (animationName, startStatus, endStatus) { 917 var keyframes = this.findKeyframesRule(animationName); 918 this.removeKeyframeRule(keyframes); 919 //重新设置帧规则 920 var prefix = this.browserPrefix == "" ? "" : "-" + this.browserPrefix + "-"; 921 keyframes.appendRule("0% { " + prefix + "transform: " + startStatus + "; }"); 922 keyframes.appendRule("100% { " + prefix + "transform: " + endStatus + "; }"); 923 }, 924 createSheets: function () { 925 // 创建 <style> 标签 926 var style = document.createElement("style"); 927 // 可以添加一个媒体(/媒体查询,media query)属性 928 // style.setAttribute("media", "screen") 929 // style.setAttribute("media", "only screen and (max-width : 1024px)") 930 // 对WebKit hack :( 931 style.appendChild(document.createTextNode("")); 932 // 将 <style> 元素加到页面中 933 document.head.appendChild(style); 934 return style.sheet; 935 }, 936 prefixedEvent: function (element, type, callback, isAdd) { 937 var pfx = ["webkit", "moz", "MS", "o", ""]; 938 for (var p = 0; p < pfx.length; p++) { 939 if (!pfx[p]) type = type.toLowerCase(); 940 if (isAdd) 941 element.addEventListener(pfx[p] + type, callback, false); 942 else 943 element.removeEventListener(pfx[p] + type, callback, false); 944 } 945 }, 946 start: function () { 947 this.css(); 948 this.prefix(); 949 this.create3dScene(); 950 this.createMagicCube(); 951 this.drawBackGroundColor(); 952 this.bindAnimationEvent(); //绑定动画播放完成事件 953 if (this.autoPlay) 954 this.moveMagicCube(); //立即开始动画 955 // this.timer = setInterval(this.timerFun, 100); 956 }, 957 timerFun: function () { 958 var _this = cache.magicCube; 959 if (_this.isRunning >= _this.dimension) { 960 for (var i = 0, len = _this.cubeMoveQueue.length; i < len; i++) { 961 var animation = _this.cubeMoveQueue.shift(); 962 animation.cube.Box.style[_this.browserPrefix + "Animation"] = animation.rule + " " + _this.delay + "s linear 1"; // Chrome, Safari 和 Opera 代码 963 } 964 } 965 }, 966 css: function () { 967 var d = document, 968 doc = d.documentElement, 969 body = d.body; 970 this.clientWidth = doc.clientWidth; 971 this.clientHeight = doc.clientHeight; 972 if (d.compatMode != "CSS1Compat") { 973 this.clientWidth = body.clientWidth; 974 this.clientHeight = body.clientHeight; 975 } 976 // console.log(this.width +'////'+ this.height) 977 }, 978 random: function (min, max) { 979 return (Math.random() * (max - min + 1) + min) >> 0; 980 }, 981 prefix: function () { 982 var N = navigator.appName, ua = navigator.userAgent, tem; 983 var M = ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i); 984 if (M && (tem = ua.match(/version\/([\.\d]+)/i)) != null) M[2] = tem[1]; 985 M = M ? [M[1], M[2]] : [N, navigator.appVersion, '-?']; 986 M = M[0]; 987 if (M == "Chrome") { this.browserPrefix = "webkit"; } 988 if (M == "Firefox") { this.browserPrefix = "Moz"; } 989 if (M == "Safari") { this.browserPrefix = "webkit"; } 990 if (M == "MSIE") { this.browserPrefix = "ms"; } 991 } 992 993 }; 994 }(window)); 995 //自定义简单筛选器 996 (function () { 997 var window = this, 998 selector = window.$ = function (sign, type) { 999 return new extend.init(sign, type); 1000 }; 1001 extend = $.prototype = { 1002 init: function (sign, type) { 1003 this.targets = []; 1004 switch (type) { 1005 case "id": this.targets.push(document.getElementById(sign)); break; 1006 case "name": this.targets = document.getElementsByName(sign); break; 1007 case "class": this.targets = document.getElementsByClassName(sign); break; 1008 case "tag": this.targets = document.getElementsByTagName(sign); break; 1009 } 1010 this.each(function (item, i) { 1011 this[i] = item; 1012 }) 1013 return this; 1014 }, 1015 val: function (value) { 1016 if (value == undefined) 1017 return this.targets[0].value; 1018 else if (this.targets != null) 1019 this.each(function (item) { 1020 if (item.tagName== "INPUT") { 1021 item.value = value; 1022 } 1023 }); 1024 }, 1025 Number: function () { 1026 return parseInt(this.val()) 1027 }, 1028 each: function (fun) { 1029 if (fun != undefined && typeof (fun) == "function") { 1030 for (var i = 0, len = this.targets.length; i < len; i++) { 1031 fun.call(this, this.targets[i], i) 1032 } 1033 } 1034 }, 1035 setClass: function (className) { 1036 if (this.targets != null) 1037 this.each(function (item) { 1038 item.className = className; 1039 }); 1040 }, 1041 addClass: function (className) { 1042 if (this.targets != null) 1043 this.each(function (item) { 1044 item.classList.add(className); 1045 }); 1046 }, 1047 removeClass: function (className) { 1048 if (this.targets != null) 1049 this.each(function (item) { 1050 item.classList.remove(className); 1051 }); 1052 } 1053 }; 1054 extend.init.prototype = extend;//实现了可扩展性 1055 })(); 1056 </script> 1057 </head> 1058 <body style="background-color: black"> 1059 <style type="text/css"> 1060 body 1061 { 1062 margin: 0; 1063 padding: 5px; 1064 font-family: 微软雅黑; 1065 font-size: 14px; 1066 } 1067 1068 .head 1069 { 1070 border: 1px solid #333; 1071 background-color: #222; 1072 color: #aaa; 1073 position: relative; 1074 overflow: hidden; 1075 padding-bottom: 5px; 1076 top: 0; 1077 } 1078 1079 #container 1080 { 1081 position: relative; 1082 height: 600px; 1083 margin: 0 auto; 1084 overflow: auto; 1085 padding: 0; 1086 top: 0; 1087 clear: both; 1088 background-color: #111; 1089 } 1090 1091 .head .bind 1092 { 1093 float: left; 1094 width: 163px; 1095 margin-top: 5px; 1096 border-right-color: #333; 1097 border-right-style: solid; 1098 border-right-width: 1px; 1099 } 1100 .head .bind .status 1101 { 1102 border: none; 1103 } 1104 .head .bind .left 1105 { 1106 text-align: right; 1107 float: left; 1108 width: 87px; 1109 overflow: hidden; 1110 padding-right: 3px; 1111 } 1112 1113 .head .bind .right 1114 { 1115 text-align: left; 1116 float: left; 1117 width: 68px; 1118 padding: 0; 1119 padding: 0 5px 0 0; 1120 } 1121 1122 .head .bind .right input 1123 { 1124 width: 40px; 1125 text-align: center; 1126 background-color: #bbb; 1127 margin-right: 3px; 1128 border: 1px solid #dedede; 1129 } 1130 1131 .head .bind .button 1132 { 1133 text-decoration: none; 1134 background-color: #005588; 1135 color: #bbf; 1136 border: 1px solid #0066ff; 1137 padding: 1px 12px 1px 12px; 1138 margin-right: 2px; 1139 } 1140 1141 .head .bind .disbutton 1142 { 1143 text-decoration: none; 1144 background-color: #555; 1145 color: #bbb; 1146 border: 1px solid #777; 1147 padding: 1px 12px 1px 12px; 1148 margin-right: 2px; 1149 } 1150 1151 .head .bind .button:hover 1152 { 1153 background: #0088aa; 1154 color: #eee; 1155 } 1156 </style> 1157 <div id="head" class="head"> 1158 <div class="bind"> 1159 <div class="left"> 1160 魔方阶数: 1161 </div> 1162 <div class="right"> 1163 <input id="dimension" type="text" value="3" /> 1164 </div> 1165 </div> 1166 <div class="bind"> 1167 <div class="left"> 1168 立方体宽度: 1169 </div> 1170 <div class="right"> 1171 <input id="width" type="text" value="100" />px 1172 </div> 1173 </div> 1174 <div class="bind"> 1175 <div class="left"> 1176 立方体间隔: 1177 </div> 1178 <div class="right"> 1179 <input id="margin" type="text" value="10" />px 1180 </div> 1181 </div> 1182 <div class="bind"> 1183 <div class="left"> 1184 透明度: 1185 </div> 1186 <div class="right"> 1187 <input id="opacity" type="text" value="90" />% 1188 </div> 1189 </div> 1190 <div class="bind"> 1191 <div class="left"> 1192 打乱步数: 1193 </div> 1194 <div class="right"> 1195 <input id="rollbackPoint" type="text" value="10" /> 1196 </div> 1197 </div> 1198 <div class="bind"> 1199 <input id="autoPlay" type="radio" value="0" />自动播放<input id="operater" type="radio" 1200 value="1" />手动操作 1201 </div> 1202 <div class="bind" style="width: 254px"> 1203 <a class="button" id="reset" href="javascript:void(0)">重 置</a> <a class="button" 1204 id="random" href="javascript:void(0)">随机打乱</a> <a class="button" id="back" href="javascript:void(0)"> 1205 返回上一步</a> 1206 </div> 1207 <div class="bind" style="border: none;"> 1208 <div class="left"> 1209 已走步数: 1210 </div> 1211 <div class="right"> 1212 <input id="steps" readonly type="text" value="0" /> 1213 </div> 1214 </div> 1215 </div> 1216 <div id="container" title="可按下鼠标移动来旋转魔方"> 1217 </div> 1218 <script> 1219 var cube = null; 1220 var autoPlay = true; 1221 var container = $("container", "id")[0]; 1222 var screenHeight = document.compatMode != "CSS1Compat" ? document.body.clientHeight : document.documentElement.clientHeight; 1223 var Height = screenHeight - document.getElementById("head").offsetHeight - 10; 1224 container.style.height = Height + "px"; 1225 var inputs = $("input", "tag"); 1226 //输入检查事件 1227 function inputBlur(e) { 1228 var patten = new RegExp(/^[0-9]*[0-9][0-9]*$/); 1229 if (!patten.test(this.value)) 1230 { this.value = ""; return } 1231 switch (this.id) { 1232 case "dimension": if (this.value < 1 || this.value > 10) { this.value = ""; return } break; 1233 case "width": if (this.value < 10 || this.value > 1000) { this.value = ""; return } break; 1234 case "margin": if (this.value > 1000) { this.value = ""; return } break; 1235 case "opacity": if (this.value > 100) { this.value = ""; return } break; 1236 case "rollbackPoint": if (this.value > 100) { this.value = ""; return } break; 1237 1238 } 1239 build(); 1240 } 1241 //自动手动事件 1242 function radioChange(select) { 1243 for (var i = 0; i < radios.length; i++) { 1244 if (radios[i] != select) { 1245 radios[i].checked = false; 1246 } 1247 } 1248 if (select.value == "0") { 1249 $("a", "tag").setClass("disbutton"); 1250 $("reset", "id")[0].removeEventListener("click", resetCube, false); 1251 $("random", "id")[0].removeEventListener("click", randomConfuse, false); 1252 $("back", "id")[0].removeEventListener("click", backLastStep, false); 1253 autoPlay = true; 1254 } 1255 else { 1256 $("a", "tag").setClass("button"); 1257 $("reset", "id")[0].addEventListener("click", resetCube, false); 1258 $("random", "id")[0].addEventListener("click", randomConfuse, false); 1259 $("back", "id")[0].addEventListener("click", backLastStep, false); 1260 autoPlay = false; 1261 } 1262 if (cube != null) { 1263 cube.autoPlay = autoPlay; 1264 cube.delay = autoPlay ? 2 : 0.5; 1265 if (autoPlay) { 1266 cube.moveMagicCube(); 1267 cube.isRandomConfuse = false; 1268 } 1269 } 1270 1271 } 1272 //重置事件 1273 function resetCube(e) { 1274 autoPlay = false; 1275 build(); 1276 } 1277 //随机打乱事件 1278 function randomConfuse(e) { 1279 if (cube != null) { 1280 cube.isRandomConfuse = true; 1281 cube.autoPlay = true; 1282 cube.delay = 0.5; 1283 cube.rollbackPoint = $("rollbackPoint", "id").Number(); 1284 cube.rollMoveStack = []; 1285 cube.moveMagicCube(); 1286 } 1287 } 1288 //返回上一步事件 1289 function backLastStep(e) { 1290 if (cube != null) { 1291 cube.moveRollBackCube(); 1292 } 1293 } 1294 var radios = []; 1295 inputs.each(function (item) { 1296 if (item.type == "text") 1297 item.addEventListener("change", inputBlur, false); 1298 else if (item.type == "radio") { 1299 item.addEventListener("change", function (e) { radioChange(this); }, false); 1300 radios.push(item); 1301 } 1302 }); 1303 var steps_input = $("steps", "id"); 1304 function statusCallBack(steps) { 1305 steps_input.val(steps); 1306 } 1307 function build() { 1308 if (cube != null) 1309 container.removeChild(cube.Scene); 1310 var radio; 1311 cube = new magicCube({ parent: container, dimension: $("dimension", "id").Number(), cubWidth: $("width", "id").Number(), cubHeight: $("width", "id").Number(), marginLeft: $("margin", "id").Number(), marginTop: $("margin", "id").Number(), marginZ: $("margin", "id").Number(), cubZ: $("width", "id").Number(), opacity: $("opacity", "id").Number() / 100, delay: autoPlay ? 2 : 0.5, autoPlay: autoPlay, rollbackPoint: $("rollbackPoint", "id").Number(), statusCallBack: statusCallBack }); 1312 steps_input.val(0); 1313 1314 } 1315 if (autoPlay) { 1316 radio = $("autoPlay", "id")[0]; 1317 radio.checked = true; 1318 } 1319 else { 1320 radio = $("operater", "id")[0] 1321 radio.checked = true; 1322 } 1323 radioChange(radio); 1324 build(); 1325 </script> 1326 </body> 1327 </html>