טיול באתר תלת ממדי בעזרת HTML5 ו CSS3 בלבד.

לאחר שהתודעתי ליכולות 3D שטמונות ב CSS3. לא יכולתי להתאפק ולנסות ליצור אתר בו ניתן להסתובב בגוף ראשון (FPS) ללא WebGL. ההבדל הגדול בשימוש בטכנולוגיה הזאת, בניגוד ל WebGL, הוא בכך שהאלמנטים בעולם ( הקירות בדוגמה שלי) הם למעשה DIV פשוט של HTML.
כך כל מי שיודע קצת HTML יכול לבנות אתר שהוא בעצם עולם שבו המבקר מסתובב בתלת מימד.
אכן הטכנולוגיה הזאת עדיין בחיתולים וישימה במלואה רק ב Chrome ו safari. אבל סביר להניח שבמהרה כל השאר יתמכו גם הם.
היתרון הגדול על פני WebGL. שהוא נתמך בספארי על IOS ( איפון ואיפד ).

לחץ על התמונה בכדי לעבור לדמו

3D HTML5 CSS3 FPS Movements

טיול באתר תלת ממדי בעזרת HTML5 ו CSS3 בלבד


להורדת הקוד לחץ פה
להתעללות בקוד דרך FIDDLE לחץ פה

בואו נביט תחילה על ה HTML:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8" />
    <title>3D CSS First Person look by Ronen Tsamir</title>
    <meta name="author" content="Ronen Tsamir" />
    <link rel="stylesheet" href="world.css" />
    <link rel="stylesheet" href="style.css" />
    <script type="text/javascript" src="src/RtUtils.js"></script>
    <script type="text/javascript" src="src/math/gl-matrix.js"></script>
    <script>
       if(!RTUtils.isBrowser(["chrome","safari"]))
           window.location = "fallback.html"
    </script>
</head>
<body>
<div id="wrapper">
<div class="viewport" contentEditable="false">
    <section class="cube" id="world">
        <div class="top"></div>
        <"div class="front>
            <h1>
                <p>This 3D Room, FPS Style, build using HTML5 and CSS3</p>
                <p> perspective, 3D transform and Rotation via transition.</p>
                <p>No webGL</p>
            </h1>
        </div>
        <div class="back">
            <h2>Salvador Dali Wall</h2>
            <img src="assets/Dali2.jpg"  width="450" height="280"/>
            <img src="assets/Dali4.jpg"  width="450" height="280"/>
            <img src="assets/Dali8.jpg"  width="450" height="280"/>
            <img src="assets/Dali10.jpg"  width="450" height="280"/>
         </div>
        <div class="right">
            <img src="assets/me.jpg"  width="300" height="300"/>
            <h2><p>By Ronen Tsamir</p>
            <time>01th March 2012</time></h2>
            <a href="http://ronen.tsamir.net/?page_id=1282" target="_top"> To The Post</a> or
            <a href="http://jsfiddle.net/Wp5LW/" target="_blank">   Fiddle me </a>

        </div>
        <div class="left">
            <h2> Some others posts</h2>
            <a href="http://ronen.tsamir.net/?page_id=255" target="_blank"> <img src="assets/flash10api1.jpg"  width="300" height="200"/></a>

            <a href="http://ronen.tsamir.net/?page_id=1143" target="_blank"><img src="assets/bowGame.jpg"  width="300" height="200"/> </a>
             <a href="http://ronen.tsamir.net/?page_id=290" target="_blank"> <img src="assets/walls.jpg"  width="300" height="200"/> </a>
        </div>
        <div class="bottom"></div>
    </section>
</div>
</div>
    <script src="world.js"></script>
</body>
</html>
ניתן ליראות שמדובר ב HTML רגיל שמחובר אליו CSS רגיל style.css

ההבדל היחיד הוא שיש אלמנטים שמוצמד אליהם Class של כיוון ( top, front, back , right, left, bottom )
ה Classes האלה מוגדרים ב world.css
ה css הזה למעשה מאתחל את הפרמטרים של ההטלה הפרספקטיבית ומציב כל DIV במקום ובזוית שלו במרחב.

נסתכל לדוגמה בקיר הימני:

.left{
    height: 500px;
    width: 2000px;
    -webkit-transform:  rotateY(  90deg ) translateZ(-1000px );
    -moz-transform:  rotateY(  90deg ) translateZ(-1000px );
    -o-transform:  rotateY(  90deg ) translateZ(-1000px );
    transform:  rotateY(  90deg ) translateZ(-1000px );
}

במקטע הזה הקיר השמאלי מסתובב 90 מעלות על ציר ה Y ואז מוזז 1000 פיקסלים על ציר ה Z וכך הוא מגיע למקומו בחדר.
מאחר שההזזה על ציר ה Z נעשית לאחר הסיבוב, למעשה הקיר מוזז על ציר ה X של העולם, כלומר וקטור המיקום של הקיר בעולם הוא [1000,0,0]

כך ממקמים את כל הקירות של החדר במקום ( אפשר כמובן למקם עוד הרבה אלמנטים חוץ מהקירות, אני בחרתי במבנה של חדר סגור בשביל להשאיר את הדוגמה פשוטה)
מתקבל חדר שמידותיו הם 2000 על 2000 פיקסלים בגובה של 500 פיקסלים.

למעשה עד כאן יצרנו את מבנה העולם שלנו ומה שנשאר זה להוסיף את אפשרות ההסתובבות בעולם.

כאן מצטרף למשחק הקובץ world.js

/**
 * Created with JetBrains WebStorm.
 * User: Ronen
 * Date: 13/11/12
 * Time: 23:20
 * To change this template use File | Settings | File Templates.
 */


(function () {
   
/*  The View (Camera Matrix) */
var mWorldMatrix = mat4.identity();

var mCameraRotation = vec3.create();
/* The Camera Rotation around the Y axis set to 0 */
mCameraRotation[1] =0;

/* The Camera Location Vector */
var mCameraLoc = vec3.create();

/* The Camera Location along the x axis set to 700px; */
mCameraLoc[0] = 700;
/* The Camera Location along the z axis set to 500px; */
mCameraLoc[2]= 500;



/* get the world container div. */
var mView = window.document.getElementById("world");

/* Constant - rotation sensitivity */
var ROTATION_SENSITIVITY = 300;

/* Constant - movement sensitivity */
var MOVEMENT_SENSITIVITY = 2;

/* use for the mouse control */
var mIsMouseDown = false;
/* use for the moving control */
var touchX;
var touchY;
/* init events. */
RTUtils.addEvent('touchstart', touchStart, window);
RTUtils.addEvent('touchmove', touchMove, window);
RTUtils.addEvent('touchend', touchEnd, window);
RTUtils.addEvent('mousemove', mouseMove, window);
RTUtils.addEvent('mouseup',   mouseUp, window);
RTUtils.addEvent('mousedown',   mouseDown, window);


//-----------------------------------------------------------------
/* get the Transform Property for the current browser */
var el = document.createElement('div'),
    transformProps = 'transform WebkitTransform MozTransform OTransform msTransform'.split(' '),
    transformProp = support(transformProps);


    /**
     * get the Transform Property for the current browser
     *
     * @param {array} props Array of transform types
     *
     * @returns {string} The current browser transform property
     */


function support(props) {
    for(var i = 0, l = props.length; i < l; i++) {
        if(typeof el.style[props[i]] !== "undefined") {
            return props[i];
        }
    }
}

 //-----------------------------------------------------------------
    /**
     * Get the user mouse or touch location.
     * Determine the movement delta and call the move() method with the movement delta.
     *
     * @param {number} x X screen value
     * @param {number} y Y screen value
     *
     */

 function userMovement(x,y)
 {
     console.log(x + "," + y)
     var dX = x - touchX ;
     var dY = touchY - y;
     if(Math.abs(dX) <= 1)
         dX = 0;
     if(Math.abs(dY) <= 1)
         dY = 0;
     move(dY, dX);
     touchX = x;
     touchY = y;
 }

 //-----------------------------------------------------------------
    /**
     * Move the camera
     *
     * @param {number} speedMoveX  - the forword movment speed.
     * @param {number} speedRotationY - the rotation speed around the Y axis.
     *
     */

 function move(speedMoveX,speedRotationY)
 {
    /* Add the rotation speed on the Y axis to the camera rotation vector. */
    mCameraRotation[1] += speedRotationY / ROTATION_SENSITIVITY;
     /* Compute the speed vector and add it to the camera location. */
    mCameraLoc[2] += speedMoveX * MOVEMENT_SENSITIVITY * Math.cos(-mCameraRotation[1]);
    mCameraLoc[0] += speedMoveX * MOVEMENT_SENSITIVITY * Math.sin(-mCameraRotation[1]);
     /* Check collision with the room wall. */
    setRoomCollision(mCameraLoc);

     /* prepare the camera 3D matrix. */
    var aCameraMatrix3D = mat4.identity();
    mat4.rotateY(aCameraMatrix3D, mCameraRotation[1]);
    mat4.translate(aCameraMatrix3D, [mCameraLoc[0],0, mCameraLoc[2]]);
    var aRotationOnly = mat4.identity();
    mat4.rotateY(aRotationOnly, mCameraRotation[1]);

    var aViewMatrix = mat4.identity();
    mat4.rotateY(aViewMatrix, mCameraRotation[1]);
    aViewMatrix[12] = -aCameraMatrix3D[12];
    aViewMatrix[13] = -aCameraMatrix3D[13];
    aViewMatrix[14] = -aCameraMatrix3D[14]+735;

    /* create a String from the matrix; */
    var aMatrixString = mat4.strForCSS3(aViewMatrix);
     /* apply the Matrix on the World view. */
    mView.style[transformProp] = aMatrixString;
 }
 //-----------------------------------------------------------------

    /**
     * Collide with the room walls
     *
     * @param {vec3} pCameraLoc  - the location vector of the camera.
     *
     */


 setRoomCollision = function (pCameraLoc) {
     if(pCameraLoc[2] > 950)
         pCameraLoc[2] = 950;
     if(pCameraLoc[2] < -950)
         pCameraLoc[2] = -950;
     if(pCameraLoc[0] > 1650)
         pCameraLoc[0] = 1650;
     if(pCameraLoc[0] < -250)
         pCameraLoc[0] = -250;
 };

    /**
     * All the next 6 method are regular method for touch & mouse control
     */


//-----------------------------------------------------------------
function touchStart(e)
{
    var touch = event.touches[0];
    touchX = touch.pageX;
    touchY = touch.pageY;
}
//-----------------------------------------------------------------
function touchMove(e)
{
    console.log("touchMove")
    e.preventDefault();
    var touch = event.touches[0];
    //console.log("Touch x:" + touch.pageX + ", y:" + touch.pageY);
    userMovement(touch.pageX,touch.pageY)

}
//-----------------------------------------------------------------
function touchEnd(e){}
//-----------------------------------------------------------------
function mouseMove(ev)
{
    if(!mIsMouseDown)
        return;
    ev.preventDefault();
    userMovement(ev.clientX, ev.clientY);
}
//-----------------------------------------------------------------

function mouseUp(e)
{
    e.preventDefault();
    mIsMouseDown = false;
}
//-----------------------------------------------------------------

function mouseDown(ev)
{
    console.log("mouseDown");
    ev.preventDefault();
    touchX = ev.clientX;
    touchY = ev.clientY;
    mIsMouseDown = true;

}
move(0,0);
})();

אין באפשרותי להסביר פה כל שורה ( יש תיעוד סביר בגוף הקוד ) אבל אתמקד בכמה עקרונות חשובים.

למעשה לכל אפליקציית טיול בגוף ראשון בעולם כלשהו יש שני מרכיבים עיקריים העולם ו…..אני. (כן כמו בחיים)
העולם מיוצג על ידי מבנה כלשהו, במקרה שלנו אוסף של DIVS
והמצלמה תמיד מיוצגת על ידי מטריצה 4X4. השומרת את מצב המצלמה בעולם.
המשתמש בדרך כלל משנה את המטריצה של המצלמה ( במקרה שלנו על ידי תנועת אצבע בניידים או עכבר במחשב )
וכל פעם שהמטריצה משתנה אנו מטילים אותה על העולם.

קודם כל יש צורך לקבל מצביע לעולם שלנו. וזאת אני עושה בשורה הבאה:

var mView = window.document.getElementById("world");
המתודה הבאה למעשה מחשבת את המטריצה של המצלמה ומטילה אותה על העולם:
כאשר speedMoveX ו speedRotationY מתקבלים מתוך התנועה של המשתמש.

 function move(speedMoveX,speedRotationY)
 {
    /* Add the rotation speed on the Y axis to the camera rotation vector. */
    mCameraRotation[1] += speedRotationY / ROTATION_SENSITIVITY;
     /* Compute the speed vector and add it to the camera location. */
    mCameraLoc[2] += speedMoveX * MOVEMENT_SENSITIVITY * Math.cos(-mCameraRotation[1]);
    mCameraLoc[0] += speedMoveX * MOVEMENT_SENSITIVITY * Math.sin(-mCameraRotation[1]);
     /* Check collision with the room wall. */
    setRoomCollision(mCameraLoc);

     /* prepare the camera 3D matrix. */
    var aCameraMatrix3D = mat4.identity();
    mat4.rotateY(aCameraMatrix3D, mCameraRotation[1]);
    mat4.translate(aCameraMatrix3D, [mCameraLoc[0],0, mCameraLoc[2]]);
    var aRotationOnly = mat4.identity();
    mat4.rotateY(aRotationOnly, mCameraRotation[1]);

    var aViewMatrix = mat4.identity();
    mat4.rotateY(aViewMatrix, mCameraRotation[1]);
    aViewMatrix[12] = -aCameraMatrix3D[12];
    aViewMatrix[13] = -aCameraMatrix3D[13];
    aViewMatrix[14] = -aCameraMatrix3D[14]+735;

    /* create a String from the matrix; */
    var aMatrixString = mat4.strForCSS3(aViewMatrix);
     /* apply the Matrix on the World view. */
    mView.style[transformProp] = aMatrixString;
 }

אפשר די בקלות לעקוב שלב אחרי שלב ולראות איך אני יוצר את המטריצה.
השורה האחרונה היא החשובה ביותר בה למעשה מוטלת המטריצה על העולם…… וקסם התנועה מתרחש.

לסיכום אני רוצה להודות לינון פרק שללא הקורס הנהדר שלו שחשף אותי לנפלאות ה HTML5, במיוחד במובייל,. לא הייתי מחבר את כל הקצוות למדריך זה.

כתיבת תגובה

האימייל לא יוצג באתר.

תגי HTML מותרים: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>