CSS3 製作魔方 - 玩轉魔方

来源:https://www.cnblogs.com/timeddd/archive/2019/05/15/10870822.html
-Advertisement-
Play Games

在上一篇 "《CSS3 製作魔方 形成魔方》" 中介紹了一個完整魔方的繪製實現,本文將介紹魔方的玩轉,支持上下左右每一層獨立地旋轉。先來一睹玩轉的風采。 1.一個問題 由於魔方格的位置與轉動的路徑相關,僅依靠 rotateX,rotateY,rotateZ 單個的值無法直接表明其定位。如下圖,第一個 ...


在上一篇《CSS3 製作魔方 - 形成魔方》中介紹了一個完整魔方的繪製實現,本文將介紹魔方的玩轉,支持上下左右每一層獨立地旋轉。先來一睹玩轉的風采。

1.一個問題

由於魔方格的位置與轉動的路徑相關,僅依靠 rotateX,rotateY,rotateZ 單個的值無法直接表明其定位。如下圖,第一個魔方格進行了特殊化處理。

當使用路徑 rotateY(90)->rotateY(90)->rotateX(90)->rotateY(-90)  來旋轉這個特殊魔方格時,Y 最終是 90度,X 是90 度,按路徑旋轉的結果如下圖。

它並不等於

Y  90度,X 90 度的旋轉結果:rotateY(90)->rotateX(90)

2.解決辦法

不能直接表示,就換一種方式。可以根據旋轉的方向 重算魔方格在魔方中的坐標 並重繪魔方格,而旋轉動畫效果可以採用 逆向90度重繪再 transition 回來 的方式,詳見後述。

3.自上而下實現旋轉

3.1頂層視角

縱觀魔方,實現各層的旋轉它應該開放什麼介面呢?進行旋轉需要指定的數據是什麼呢?這便形成了功能的頂層描述與介面需求:指定x,y,z軸向、給定第幾層、規定轉向完成90度的旋轉。

於是在 MagicBox 類中可以增加方法:功能介面名稱使用 Rotate,其參數為 軸向(axis)、層(level)、轉向(turn)。

其中:

  • 軸向可取值 x、y、z
  • 層根據坐標體系從0開始 至 魔方階數-1
  • 轉向因為一層只會有兩個方向,為了統一描述,總以朝軸正方向視角來分左向(left)與右向(right)。

旋轉的層所包含的具體魔方格,對於頂層而言,則只需要 對旋轉要求通知到位即可,而通知的內容為軸向(axis)、轉向(turn),以及魔方階數(dimension)。

有此描述,頂層 Rotate 方法非常簡單,功能為找出指定的層進行通知即可:

/** MagicBox.Rotate 旋轉
  * axis 軸向
  * level 層
  * turn 轉向
 **/
this.Rotate = function(axis, level, turn){
    for(var i=0; i < this.cubes.length; i++) { 
        if(this.cubes[i][axis] == level) {    // 該軸該層的才旋轉
            this.cubes[i].Rotate(axis, turn, dimension);   
        }
    }
};

3.2魔方格介面實現

3.2.1旋轉坐標變換

軸向有x,y,z,但每層的旋轉只涉及到兩個軸向,有(x,y)、(y,z)、(x,z),儘管魔方格看上去很多的,但坐標的轉換卻都遵循非常簡單的變換規律,以4階的 (x,y) 坐標轉換為例,如下,黃色為同一個坐標旋轉變換的值:

很容易得出規律,旋轉前後的坐標中,總有一個是相同的,另兩個的和是固定的,而且剛好為魔方階數減1。

假設旋轉前後坐標分別為(x1,y1)、(x2,y2),則:

往左旋轉,坐標變換規律為:x2 = y1, y2 = 3 - x1,這其中的 3 為魔方階數減1。

往右旋轉,坐標變換規律為:x2 = 3 - y1, y2 = x1。

這恰是:我成為了你,而你是我的補

於是有了以下轉換過程:

/** 坐標轉換
 * axis 軸向
 * turn 轉向
 * dimension 階數
 **/
this.TransCoordinate = function(axis, turn, dimension){ 
    if(axis == 'x'){              
        if( turn == 'left' ){
            var oriy = this.y;
            this.y = this.z;
            this.z = dimension - 1 - oriy;
        } else {
            var oriz = this.z;
            this.z = this.y;
            this.y = dimension - 1 - oriz;
        }
    } else if(axis == 'y'){
        if( turn == 'right' ){
            var orix = this.x;
            this.x = this.z;
            this.z = dimension - 1 - orix;
        } else {
            var oriz = this.z;
            this.z = this.x;
            this.x = dimension - 1 - oriz;
        }
    } else if(axis == 'z'){ 
        if( turn == 'right' ){
            var orix = this.x;
            this.x = this.y;
            this.y = dimension - 1 - orix;
        } else {
            var oriy = this.y;
            this.y = this.x;
            this.x = dimension - 1 - oriy;
        }
    } 
}

3.2.2旋轉重繪

在魔方格裡,通過版面(block)在旋轉方向上的變換達到旋轉的效果,方式為根據旋轉方向同向移動方向即可。

/** 將各 block 調整位置,重繪魔方格
* axis 軸向
* turn 轉向
**/
this.ReDrawBlocks = function(axis, turn){
    var xyzDirects = [];
    xyzDirects['x'] = ["front", "up", "back", "down"]; 
    xyzDirects['y'] = ["front", "right", "back", "left"]; 
    xyzDirects['z'] = ["up", "right", "down", "left"]; 
    var curDirects = xyzDirects[axis];

    for(var i=0; i < this.blocks.length; i++) { 
        var index = curDirects.indexOf( this.blocks[i].direct );
        if(index > -1){
            var newIndex = turn == 'left' ? (index + 1) % 4 : (index + 4 - 1) % 4;
            this.blocks[i].direct = curDirects[newIndex]; 
            this.blocks[i].DrawIn(this.Element);
        }    
    }
}

3.2.3動畫體現

調整好的魔方格,逆向旋轉90度,則外觀保持跟旋轉前一樣,這就有了進行動畫的基礎,動畫的實質就是欺騙眼睛。

然後,利用 transition 讓其過濾到不旋轉的(即調整好的)外觀即可達到效果。這樣的好處是,魔方格不論在什麼位置,每次相關的旋轉角度僅是逆向的 90 度,問題局部化時,事情就變得簡單

// 先停止動畫效果,逆向 90 度,此時外觀跟旋轉前一致
this.Element.style["transition"] = "";
var rotateDegs = new Object();
rotateDegs[axis] = (turn == 'left' ? -90 : 90);  
this.Element.style["transform"] = this.FormatTransform(rotateDegs); 
// 旋轉原點旋轉的層都需要以魔方的中心點旋轉
// 旋轉原點是以元素自身來計算的,因所有魔方格都是從(0,0,0)平衡的,因此計算結果都一樣
var centerX = this.blockSize * dimension / 2;
var centerY = this.blockSize * dimension / 2;
var centerZ = -this.blockSize * dimension / 2;
this.Element.style["transformOrigin"] = centerX + "px " + centerY + "px " + centerZ + "px";

// 這樣才能觸發動畫
setTimeout(function(obj){
    return function(){
        obj.Element.style["transform"] = obj.FormatTransform(); 
        obj.Element.style["transition"] = "transform 0.3s";  // 0.3 秒
    };
}(this), 1);

// 以下為transfrom 屬性格式化的一個方法,這個屬性值太長了又是旋轉平移多組合
// 格式化 transform 屬性
// css3 把旋轉與平移混一起(真不好用)
this.FormatTransform = function (rotateDegs){ 
    var rotatePart = "rotateX(0deg) rotateY(0deg) rotateZ(0deg)";
    if(rotateDegs){
        rotatePart = "rotateX(" + (rotateDegs.x | 0) + "deg) rotateY(" + (rotateDegs.y | 0) + "deg) rotateZ(" + (rotateDegs.z | 0) + "deg)";
    }   

    return rotatePart + " translate3d(" + (this.x * this.blockSize) + "px," + (this.y * this.blockSize) + "px,-" + (this.z * this.blockSize) + "px) ";      
} 

4.旋轉控制實例效果

有了這個旋轉的方法,通過給定一組旋轉參數序列,可以讓魔方自動運轉,並且自動複原。

function onload(){

    //* 魔方繪製示例
    var magicBox = new MagicBox(5, 50);
    magicBox.DrawIn( document.querySelector(".wrap") ); 

    var rotates = GenRotateActions(5, 10);
    for(var i=0; i<rotates.length; i++){    
        setTimeout(function(magicBox, rotate){
            return function(){
                magicBox.Rotate(rotate.axis, rotate.level, rotate.turn);
            };
        }(magicBox, rotates[i]), 500 * i);
    }

    /* 反向旋轉,就能複原魔方 */
    for(var i=0; i<rotates.length; i++){    
        setTimeout(function(magicBox, rotate){
            return function(){
                magicBox.Rotate(rotate.axis, rotate.level, (rotate.turn == 'left' ? 'right' : 'left'));
            };
        }(magicBox, rotates[rotates.length -1 - i]), 5500 + 500 * i);
    }
}

/** 產生一個指定數量的旋轉序列數組
 * dimension 魔方階數
 * count 序列數量
 **/
function GenRotateActions(dimension, count){
    var result = [];
    for(var i=0; i<count; i++){
        result[i] = {
            axis  : ['x','y','z'][Math.floor(Math.random() * 3)],
            level : Math.floor(Math.random() * dimension), 
            turn  : ['left','right'][Math.floor(Math.random() * 2)]
        };
    }
    return result;
}

效果如下:

5.小結與附件

尋找共性向上抽象,形成統一的處理模式能夠讓處理模型變得簡單。

本文實例,支持動態建立多階的魔方,但對參數缺少邊界檢查,同時對旋轉的是否結束未作標記或判斷,感興趣的朋友可以進一步完善它。

本實例代碼發佈在 https://github.com/triplestudio/magicbox


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 轉載請註明出處:葡萄城官網,葡萄城為開發者提供專業的開發工具、解決方案和服務,賦能開發者。原文出處:https://wanago.io/2018/08/13/webpack-4-course-part-seven-decreasing-the-bundle-size-with-tree-shakin ...
  • /Users/qw/Desktop/屏幕快照 2019-05-16 上午3.04.59.png ...
  • 1.Vue的介紹 Vue是一套用於構建用戶界面的漸進式框架。 註意:Vue是一個框架,相對於jq庫來說,是由本質的區別的:https://cn.vuejs.org/ Vue不支持IE8及一下版本,因為Vue使用了IE8無法模擬的 ECMAScript 5 特性。但它支持所有相容 ECMAScript ...
  • img標簽 只要設置了src屬性, 就會開始下載,因此可以使用這個特性,配合display:none,默默的下載一些圖片,用的時候直接用,快了那麼一丟丟~ 註意:不一定要添加到文檔後才會開始下載,是只要一設置src屬性就會下載:觀察下麵代碼: script標簽 與圖像不同! 這個` ...
  • 弦生成器(Chord Generator) 弦生成器(Chord Generator)根據兩段弧來繪製弦,共有五個訪問器,分別為source()、target()、radius()、startAngle()、endAngle(),預設都返回與函數名稱相同的變數。如果都使用預設的訪問器,則要繪製一段弧 ...
  • 一、簡介: JQuery是一個JS代碼倉庫,是一個快速度的簡介的JS庫,可以簡化查詢DOM對象,處理時間,製作動畫,處理Ajax交互過程。 二、優勢: 1、體積小,使用靈巧(只需引入一個JS文件); 2、方便的選擇頁面元素(模仿CSS選擇器更精確、靈活); 3、動態更改頁面樣式/頁面內容(操作DOM ...
  • 一、數組: 一組數據的集合; 二、JS中數組的特點: 1、數組定義時無需指定數據類型; 2、數組定義時可以無需指定數組長度; 3、數組可以存儲任何類型的數據; 4、一般是相同的數據類型; 三、數組的創建方式: 四、數組的操作: 數組名.方法 1、push() 向數組末尾添加新的數組項,返回值新數組的 ...
  • 多分支IF語句和跳樓現象 用戶輸入成績: 大於85 顯示優秀; 大於70 顯示良好; 大於60 顯示及格; 小於60 顯示不及格 格式: if(條件表達式1){ 滿足條件表達式1執行的語句 }else if(條件表達式2){ 滿足條件表達式2執行的語句 }else if(條件表達式3){ 滿足條件表 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...