纯HTML+JS+CSS动画写的3D魔方 (实现了简单的旋转操作)

直接看效果, 这是正常的旋转

video

但转着转着就乱了^_^

v1

下面直接上代码:

HTML部分:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
    <div class="cubes" id="cubes"></div>
</body>
</html>
 
CSS部分:

*{pxdding:0;margin:0;}
.cubes{position:relative;z-index:0;left:0px;top:270px;transform-style: preserve-3d;transform: rotateX(-30deg) rotateY(-30deg);}
.cube {position:absolute;left:0;top:0;width: 100px;height: 100px;transform-style: preserve-3d;}
.cube span {box-sizing:border-box;border:5px solid #000;border-radius:10px;display: block;width: 100px;height: 100px;position: absolute;font-size:30px;text-align:center;line-height:100px;cursor:default;}
.cube .in_front {background:green;transform: rotateY(0deg) translateZ(50px);color:#fff;}
.cube .in_back {background:yellow;transform: translateZ(-50px) rotateY(180deg);}
.cube .in_left {background:cyan;transform: rotateY(-90deg) translateZ(50px);}
.cube .in_right {background:#fcf;transform: rotateY(90deg) translateZ(50px);}
.cube .in_top {background:purple;translateY(50px);transform: rotateX(90deg) translateZ(50px);color:#fff;}
.cube .in_bottom {background:red;transform: rotateX(-90deg) translateZ(50px);color:#fff}

 
JS部分:
var tmpl = `<div class="cube" id="{ID}"><span class="in_front">{F}</span><span class="in_back">{B}</span><span class="in_left">{L}</span><span class="in_right">{R}</span><span class="in_top">{T}</span><span class="in_bottom">{M}</span></div>`;
 
var str = '';
var txts = {
      '020' : '9←', '050' : '6←', '080' : '3←', '120' : '8←', '150' : '5←', '180' : '2←', '220' : '7←', '250' : '4←', '280' : '1←', // white
      '001' : 'S↓', '031' : 'T↓', '061' : 'U↓', '101' : 'V↓', '131' : '*↓', '161' : 'W↓', '201' : 'X↓', '231' : 'Y↓', '261' : 'Z↓', // cyan
      '002' : 'L←', '012' : 'K←', '022' : 'J←', '102' : 'O←', '112' : 'N←', '122' : 'M←', '202' : 'R←', '212' : 'Q←', '222' : 'P←', // purple
      '063' : 'A→', '073' : 'B→', '083' : 'C→', '163' : 'D→', '173' : 'E→', '183' : 'F→', '263' : 'G→', '273' : 'G→', '283' : 'I→', // red
      '004' : '9←', '014' : '8←', '024' : '7←', '034' : '6←', '044' : '5←', '054' : '4←', '064' : '3←', '074' : '2←', '084' : '1←', // green
      '225' : '9←', '215' : '8←', '205' : '7←', '255' : '6←', '245' : '5←', '235' : '4←', '285' : '3←', '275' : '2←', '265' : '1←'  // yellow
    };
var cutewidth = 100; //正方形
for(i=0; i<27; i++){
    x = parseInt(i / 9);
    y = i % 9;
    l = y % 3;
    t = parseInt(y / 3);
    tx = l*cutewidth;
    ty = t*cutewidth;
    tz = -x*cutewidth;
    id = "cube"+ i +"\" tx='"+tx+"' ty='"+ty+"' tz='"+tz+"' rx='0' ry='0' rz='0' style=\"transform:translateX("+tx+"px) translateY("+ty+"px) translateZ("+tz+"px);";
    str += tmpl.replace("{ID}", id).replace("{F}",txts[''+x+y+'4']?txts[''+x+y+'4']:'').replace("{B}",txts[''+x+y+'5']?txts[''+x+y+'5']:'').replace("{L}",txts[''+x+y+'1']?txts[''+x+y+'1']:'').replace("{R}",txts[''+x+y+'0']?txts[''+x+y+'0']:'').replace("{T}",txts[''+x+y+'2']?txts[''+x+y+'2']:'').replace("{M}",txts[''+x+y+'3']?txts[''+x+y+'3']:'');
}
$('cubes').innerHTML = str;
$('cubes').onselectstart = function(){return false;}
 
 
function $(id){return document.getElementById(id);}
 
var mousex = -1, mousey = -1, mousepid = '';
$('cubes').addEventListener('mousedown', mouseDown);
$('cubes').addEventListener('mousemove', mouseUp);
//$('cubes').addEventListener('touchstart', mouseDown, { passive: false });
//$('cubes').addEventListener('touchmove', touchmove, { passive: false });
//$('cubes').addEventListener('touchend', mouseUp, { passive: false });
 
function mouseDown(e){
  if (e.type === 'touchstart') {
    e.preventDefault();
    e = e.touches[0];
  }
  var tag = e.target.tagName.toLowerCase();
  var pid = e.target.parentNode.id;
  var prect = $('cubes').parentNode.getBoundingClientRect();
  if((tag == 'span') && (pid.substring(0,4) == 'cube')){
    mousex = e.clientX - prect.left;
    mousey = e.clientY - prect.top;
    mousepid = pid;
//console.log('['+mousex+','+mousey+']'); //可用于下面chkInReck四角定位用
  }
}
 
var nowe = null;
function touchmove(e){
  nowe = e.touches[0];
}
 
function mouseUp(e) {
  if (e.type === 'touchend') {
    e.preventDefault();
    e = nowe;
  }
  if(mousex < 0 || mousey < 0) return; //不在魔方内
  var x = e.clientX - $('cubes').parentNode.parentNode.getBoundingClientRect().left - mousex;
  var y = e.clientY - $('cubes').parentNode.parentNode.getBoundingClientRect().top - mousey;
  if((Math.abs(x) < 5) && (Math.abs(y) < 5)) return; //小范围移动当成点击
  var d, f, a, b, c, p; //方向,面, abc临时变量
  var pid = parseInt(mousepid.replace('cube',''));
  f = chkInRect(mousex,mousey);
  a = pid % 3; //右中右三层
  b = parseInt(pid / 9); //前中后三层
  c = parseInt((pid % 9) / 3); //上中下三层
  mousex = mousey = -1; mousepid = '';
  
  if(Math.abs(x) <= Math.abs(y)) {
    if(y < 0){ d = 'duu'; } else { d = 'dud'; } //上下移动
  }else{
    if(x > 0){ d = 'rlr'; } else { d = 'rll'; } //左右移动
  }
  if(f == '') { return; }
  if(f == 'f'){
    if(d[0] == 'r'){
      if(c == 0){ p = 't'; }
      if(c == 1){ p = 'm'; }
      if(c == 2){ p = 'b'; }
    }else{
    if(a == 0){ p = 'l'; }
    if(a == 1){ p = 'm'; }
    if(a == 2){ p = 'r'; }
    }
  }
  if((f == 'r')){
    if(d[0] == 'r'){
      if(c == 0){ p = 't'; }
      if(c == 1){ p = 'm'; }
      if(c == 2){ p = 'b'; }
    }else{
    if(b == 0){ p = 'l'; }
    if(b == 1){ p = 'm'; }
    if(b == 2){ p = 'r'; }
    }
  }
  if(f == 't'){
    if(d[0] == 'r'){
      if(b == 0){ p = 'b'; } //顶层后层作为上
      if(b == 1){ p = 'm'; } //中
      if(b == 2){ p = 't'; } //顶层前层作为下
    }else{
    if(a == 0){ p = 'l'; }
    if(a == 1){ p = 'm'; }
    if(a == 2){ p = 'r'; }
    }
  }
  fun = ''+f+p+d[2]+'3d';
  console.log(fun);
  exec = fun+'();';
  if(window.hasOwnProperty(fun)) eval(exec);
}
 
function chkInRect(x, y){
  //注意: 网页坐标与数学坐标有些区别, 网页坐标是左上角为[0,0], 数学坐标是左下角是[0,0],
  //     所以数学坐标图形是网页坐标图形的垂直镜像(向上翻就是了)
  var arrt = [[161,18]/*上*/,[411,90]/*右*/,[269,213]/*下*/,[19,142]/*左*/]; //top
  var arrf = [[17,142]/*上左*/,[268,215]/*上右*/,[268,465]/*下右*/,[17,394]/*下左*/]; //front
  var arrr = [[276,213]/*上左*/,[420,90]/*上右*/,[420,340]/*下右*/,[276,463]/*下左*/]; //right
  if((x > arrf[0][0]) && (x < arrf[1][0])){ //front
    y0 = parseInt(arrf[0][1] + (arrf[1][1] - arrf[0][1]) * (x - arrf[0][0]) / (arrf[1][0] - arrf[0][0]));
    y1 = parseInt(arrf[2][1] - (arrf[2][1] - arrf[3][1]) * (arrf[2][0] - x) / (arrf[2][0] - arrf[3][0]));
    if((y >= y0) && (y <= y1)) return 'f';
  } else if((x > arrr[0][0]) && (x < arrr[1][0])){ //right
    y0 = parseInt(arrr[0][1] - (arrr[0][1] - arrr[1][1]) * (x - arrr[0][0]) / (arrr[1][0] - arrr[0][0]));
    y1 = parseInt(arrr[3][1] - (arrr[3][1] - arrr[2][1]) * (x - arrr[3][0]) / (arrr[2][0] - arrr[3][0]));
    if((y >= y0) && (y <= y1)) return 'r';
  }
  //如果不在前右,再试一下是否在上面
  if((x > arrt[3][0]) && (x < arrt[1][0]) && (y > arrt[0][1]) && (y < arrt[2][1])){ //top 在菱形所在外切长方形内
    //只要点不在菱形与外切长方形形成4个外三角形内就说明在菱形内了
    if((x > arrt[0][0]) && (x < arrt[1][0])){ //边1
      y0 = arrt[0][1];
      y1 = y0 + (x - arrt[0][0]) * (arrt[1][1] - arrt[0][1]) / (arrt[1][0] - arrt[0][0]);
      if((y > y0) && (y < y1)) return '';
     }
     if((x >arrt[2][0]) && (x < arrt[1][0])){ //边2
      y1 = arrt[2][1];
      y0 = y1 - (x - arrt[2][0]) * (arrt[2][1] - arrt[1][1]) / (arrt[1][0] - arrt[2][0]);
      if((y > y0) && (y < y1)) return '';
     }
     if((x >arrt[2][0]) && (x < arrt[1][0])){ //边3
      y1 = arrt[2][1];
      y0 = y1 - (arrt[2][0] - x) * (arrt[2][1] - arrt[3][1]) / (arrt[2][0] - arrt[3][0]);
      if((y > y0) && (y < y1)) return '';
     }
     if((x >arrt[2][0]) && (x < arrt[1][0])){ //边4
      y0 = arrt[0][0];
      y1 = y0 + (arrt[0][0] - x) * (arrt[3][1] - arrt[0][1]) / (arrt[0][0] - arrt[3][0]);
      if((y > y0) && (y < y1)) return '';
     }
     //其它则在菱形内了
     return 't';
  }
  return '';
}
 
//3*3二维数组旋转90度
function rotate90(arr){
    n = 3;
    var arr1 = [];
    for(i=0; i<n; i++){
    for(j=0; j<n; j++){
        arr1[j * n + n - 1 - i] = arr[i * n + j];
    }}
    return arr1;
}
 
//x面操作
function fld3d(){
  var elsn = [0,9,18,3,12,21,6,15,24];
  roundx(elsn, 0);
}
function flu3d(){
  var elsn = [0,9,18,3,12,21,6,15,24];
  roundx(elsn, 1);
}
function fmd3d(){
  var elsn = [1,10,19,4,13,22,7,16,25];
  roundx(elsn, 0);
}
function fmu3d(){
  var elsn = [1,10,19,4,13,22,7,16,25];
  roundx(elsn, 1);
}
function frd3d(){
  var elsn = [2,11,20,5,14,23,8,17,26];
  roundx(elsn, 0);
}
function fru3d(){
  var elsn = [2,11,20,5,14,23,8,17,26];
  roundx(elsn, 1);
}
 
//y面操作
function ftr3d(){
  var elsn = [0,1,2,9,10,11,18,19,20];
  roundy(elsn, 0);
}
function ftl3d(){
  var elsn = [0,1,2,9,10,11,18,19,20];
  roundy(elsn, 1);
}
function fmr3d(){
  var elsn = [3,4,5,12,13,14,21,22,23];
  roundy(elsn, 0);
}
function fml3d(){
  var elsn = [3,4,5,12,13,14,21,22,23];
  roundy(elsn, 1);
}
function fbr3d(){
  var elsn = [6,7,8,15,16,17,24,25,26];
  roundy(elsn, 0);
}
function fbl3d(){
  var elsn = [6,7,8,15,16,17,24,25,26];
  roundy(elsn, 1);
}
 
//z面操作
function rld3d(){
    var elsn = [0,1,2,3,4,5,6,7,8];
    roundz(elsn, 0);
}
function rlu3d(){
    var elsn = [0,1,2,3,4,5,6,7,8];
    roundz(elsn, 1);
}
function rmd3d(){
    var elsn = [9,10,11,12,13,14,15,16,17];
    roundz(elsn, 0);
}
function rmu3d(){
    var elsn = [9,10,11,12,13,14,15,16,17];
    roundz(elsn, 1);
}
function rrd3d(){
    var elsn = [18,19,20,21,22,23,24,25,26];
    roundz(elsn, 0);
}
function rru3d(){
    var elsn = [18,19,20,21,22,23,24,25,26];
    roundz(elsn, 1);
}
 
function rtr3d(){ftr3d();}
function rtl3d(){ftl3d();}
function rmr3d(){fmr3d();}
function rml3d(){fml3d();}
function rbr3d(){fbr3d();}
function rbl3d(){fbl3d();}
 
function ttr3d(){rrd3d();}
function ttl3d(){rru3d();}
function tmr3d(){rmd3d();}
function tml3d(){rmu3d();}
function tbr3d(){rld3d();}
function tbl3d(){rlu3d();}
 
function tld3d(){fld3d();}
function tlu3d(){flu3d();}
function tmd3d(){fmd3d();}
function tmu3d(){fmu3d();}
function trd3d(){frd3d();}
function tru3d(){fru3d();}
 
 
function chgId(elsn, zf){ //id更换, 保证旋转后的id还是从0~27
  var elsn1 = rotate90(elsn);
  if(zf == 1){ elsn1 = rotate90(elsn1); elsn1 = rotate90(elsn1);} //总转3次,相当于反转一次
  for (var i=0; i<elsn.length; i++){
    $('cube'+elsn1[i]).id = 'cube'+elsn[i]+'n';
  }
  for (var i=0; i<elsn.length; i++){
    $('cube'+elsn[i]+'n').id = 'cube'+elsn[i];
  }
}
 
function roundx(elsn, zf){
    var ss = document.styleSheets;
    for (var i=0; i<elsn.length; i++){
      const newDiv = $('cube'+elsn[i]);
      tx = parseInt(newDiv.getAttribute('tx'));
      ty = parseInt(newDiv.getAttribute('ty'));
      tz = parseInt(newDiv.getAttribute('tz'));
      rx = parseInt(newDiv.getAttribute('rx'));
      ry = parseInt(newDiv.getAttribute('ry'));
      rz = parseInt(newDiv.getAttribute('rz'));
      newDiv.style.transformOrigin = "150px 150px -100px";
      rx1 = rx + (zf==1?90:-90); //这个是反的
      newDiv.setAttribute('rx', rx1); 
      css = `@keyframes rx${elsn[i]}${zf}{from{transform:rotateX(${rx}deg) rotateY(${ry}deg) rotateZ(${rz}deg) translateX(${tx}px) translateY(${ty}px) translateZ(${tz}px)}to{transform:rotateX(${rx1}deg) rotateY(${ry}deg) rotateZ(${rz}deg) translateX(${tx}px) translateY(${ty}px) translateZ(${tz}px)}}`;
      ss[0].insertRule(css);
      newDiv.style.animation = '0.3s linear 0s 1 normal forwards running rx'+elsn[i]+zf;
      //console.log(document.styleSheets[0]);
      setTimeout(function(id,zf){ //动画执行完后,转为css样式,保留住最终状态
        cssn = 'rx'+id+zf;
        var rules = getkeyframes(cssn);
        rules.styleSheet.deleteRule(rules.index);
        css = rules.cssRule.cssText.substring(rules.cssRule.cssText.indexOf('100% {')+6);
        css = css.substring(0, css.indexOf('}'))+'transform-origin:150px 150px -100px';
        $('cube'+id).style.cssText = css;
      }, 300, elsn[i], zf); //与动画时间一致
    }
    setTimeout((elsn,zf)=>{chgId(elsn,zf);}, 350, elsn, zf); //这个时间要比上面动画时间要长一点
}
 
function roundy(elsn, zf){
    var ss = document.styleSheets;
    for (var i=0; i<elsn.length; i++){
      const newDiv = $('cube'+elsn[i]);
      tx = parseInt(newDiv.getAttribute('tx'));
      ty = parseInt(newDiv.getAttribute('ty'));
      tz = parseInt(newDiv.getAttribute('tz'));
      rx = parseInt(newDiv.getAttribute('rx'));
      ry = parseInt(newDiv.getAttribute('ry'));
      rz = parseInt(newDiv.getAttribute('rz'));
      newDiv.style.transformOrigin = '150px 150px -100px';
      ry1 = ry + (zf==1?-90:90);
      newDiv.setAttribute('ry', ry1); 
      css = `@keyframes ry${elsn[i]}${zf}{from{transform:rotateX(${rx}deg) rotateY(${ry}deg) rotateZ(${rz}deg) translateX(${tx}px) translateY(${ty}px) translateZ(${tz}px)}to{transform:rotateX(${rx}deg) rotateY(${ry1}deg) rotateZ(${rz}deg) translateX(${tx}px) translateY(${ty}px) translateZ(${tz}px)}}`;
      ss[0].insertRule(css);
      newDiv.style.animation = '0.3s linear 0s 1 normal forwards running ry'+elsn[i]+zf;
      //console.log(document.styleSheets[0]);
      setTimeout(function(id,zf){ //动画执行完后,转为css样式,保留住最终状态
        cssn = 'ry'+id+zf;
        var rules = getkeyframes(cssn);
        rules.styleSheet.deleteRule(rules.index);
        css = rules.cssRule.cssText.substring(rules.cssRule.cssText.indexOf('100% {')+6); //取出最终状态样式
        css = css.substring(0, css.indexOf('}'))+'transform-origin:150px 150px -100px';
        $('cube'+id).style.cssText = css;
      }, 300, elsn[i], zf);
    }
    setTimeout((elsn,zf)=>{chgId(elsn,zf);}, 350, elsn, zf); //这个时间要比上面动画时间要长一点
}
 
function roundz(elsn, zf){ //elsn:对象, zf正反转: 0,1
    var ss = document.styleSheets;
    for (var i=0; i<elsn.length; i++){
      const newDiv = $('cube'+elsn[i]);
      tx = parseInt(newDiv.getAttribute('tx'));
      ty = parseInt(newDiv.getAttribute('ty'));
      tz = parseInt(newDiv.getAttribute('tz'));
      rx = parseInt(newDiv.getAttribute('rx'));
      ry = parseInt(newDiv.getAttribute('ry'));
      rz = parseInt(newDiv.getAttribute('rz'));
      rz1 = rz+(zf==1?-90:90);
      newDiv.style.transformOrigin = '150px 150px 0';
      newDiv.setAttribute('rz', rz1); 
      //newDiv.style.animation = 'rz'+elsn[i]+goZ+zf+' 0.3s linear infinite';
      //newDiv.style.animationIterationCount = 1; //动画只执行一次
      //newDiv.style.animationFillMode = 'forwards'; //动画结束后停留在最后一帧
      css = `@keyframes rz${elsn[i]}${zf}{from{transform:rotateX(${rx}deg) rotateY(${ry}deg) rotateZ(${rz}deg) translateX(${tx}px) translateY(${ty}px) translateZ(${tz}px)}to{transform:rotateX(${rx}deg) rotateY(${ry}deg) rotateZ(${rz1}deg) translateX(${tx}px) translateY(${ty}px) translateZ(${tz}px)}}`;
      ss[0].insertRule(css);
      newDiv.style.animation = '0.3s linear 0s 1 normal forwards running rz'+elsn[i]+zf;
      //console.log(document.styleSheets[0]);
      setTimeout(function(id,zf){
        cssn = 'rz'+id+zf;
        var rules = getkeyframes(cssn);
        rules.styleSheet.deleteRule(rules.index);
        css = rules.cssRule.cssText.substring(rules.cssRule.cssText.indexOf('100% {')+6);
        css = css.substring(0, css.indexOf('}'))+'transform-origin:150px 150px 0';
        $('cube'+id).style.cssText = css;
      }, 300, elsn[i], zf);
    }
    setTimeout((elsn,zf)=>{chgId(elsn,zf);}, 350, elsn, zf); //这个时间要比上面动画时间要长一点
}
 
var getkeyframes=(name)=> {
  var animation = {};
  // 获取所有的style
  var ss = document.styleSheets;
  const item = ss[0];
  for (var i = 0; i < item.cssRules.length; ++i) {
    if (item.cssRules[i] && item.cssRules[i].name && item.cssRules[i].name === name) {
      animation.cssRule = item.cssRules[i];
      animation.styleSheet = item;
      animation.index = i;
    }
  }
  return animation;
}
 
posted @ 2025-08-19 11:04  hankerstudio  阅读(63)  评论(0)    收藏  举报