News

From Traditional 3D to HTML5/CSS 3D

I had a chance recently to tackle a project that required several rotating cubes that showed updated text.  Initially, I was excited to work with some simple 3D in HTML5 using div elements, but that excitement was lost when I struggled to reconcile my years of 3D development with this type of web development.  I’m used to a “world” with a “camera” type of situation, and so I set out to try and control the HTML elements in a similar fashion.

The requirements were that the “cubes” had to have their own camera view, so to speak.  They couldn’t all use the same one since this would cause a perspective issue when you move them to the outer edges and areas of the view.

/**
 * Created by NeoRiley on 5/2/16.
 */

function run(){
    createBillboard({x:20, y:20, width: 350, height: 350});
    createBillboard({x:400, y:20, width: 350, height: 350});
    createBillboard({x:200, y:350, width: 350, height: 350});
}

function createBillboard(p_bounds){
    var world = create3DWorld(p_bounds);

    var billboard = document.createElement("div");
    billboard.id = "billboard";
    billboard.style.webkitTransformStyle = "preserve-3d";
    billboard.style.width = p_bounds.width + "px";
    billboard.style.height = p_bounds.height + "px";
    billboard.style.position = "absolute";
    billboard.style.top = "0px";
    billboard.style.left = "0px";
    billboard.style.overflow = "visible";
    billboard.className = "rotating";
    
    var str = "l0l";

    var panel_front = $createPanel(str, p_bounds);
    panel_front.style.color = "#FFFFFF";
    panel_front.style.background = "rgba(0, 255, 0, 0.5";

    var panel_back = $createPanel(str, p_bounds);
    panel_back.style.color = "#FFFFFF";
    panel_back.style.background = "rgba(0, 0, 255, 0.5";

    var panel_left = $createPanel(str, p_bounds);
    panel_left.style.color = "#FFFFFF";
    panel_left.style.background = "rgba(255, 0, 0, 0.5";

    var panel_right = $createPanel(str, p_bounds);
    panel_right.style.color = "#FFFFFF";
    panel_right.style.background = "rgba(255, 156, 0, 0.5";

    world.appendChild(billboard);
    billboard.appendChild(panel_front);
    billboard.appendChild(panel_back);
    billboard.appendChild(panel_left);
    billboard.appendChild(panel_right);

    panel_front.style.webkitTransform = "translateZ(10px)";
    panel_back.style.webkitTransform = "rotateY(180deg) translateZ(10px)";
    panel_left.style.webkitTransform = "rotateY(90deg) translateZ(10px)";
    panel_right.style.webkitTransform = "rotateY(270deg) translateZ(10px)";
}

function create3DWorld(p_bounds){
    var camera = document.createElement("div");
    camera.id = "camera";
    camera.style.webkitPerspective = "350px";
    camera.style.webkitTransformStyle = "flat";
    camera.style.width = p_bounds.width + "px";
    camera.style.height = p_bounds.height + "px";
    camera.style.position = "absolute";
    camera.style.top = p_bounds.y + "px";
    camera.style.left = p_bounds.x + "px";
    camera.style.overflow = "hidden";


    var world = document.createElement("div");
    world.id = "world";
    world.style.webkitTransformStyle = "preserve-3d";
    world.style.width = p_bounds.width + "px";
    world.style.height = p_bounds.height + "px";
    world.style.position = "absolute";
    world.style.top = "0px";
    world.style.left = "0px";
    world.style.overflow = "visible";

    document.body.appendChild(camera);
    camera.appendChild(world);

    return world;
}

//////////////////////////////////////////////////////////////////////////////////
function $createPanel(p_id, p_bounds){
    var panel = document.createElement("div");
    panel.id = "panel_" + p_id;
    panel.style.width = p_bounds.width + "px";
    panel.style.height = p_bounds.height + "px";
    panel.style.position = "absolute";
    panel.style.top = "0px";
    panel.style.left = "0px";
    //panel.style.webkitBackfaceVisibility = "hidden";
    panel.style.overflow = "hidden";

    var text = document.createElement('span');
    text.textContent = p_id;
    text.style.fontSize = "200px";
    var bounds = getBounds(p_id, {fontSize: "200px"});

    text.style.position = "absolute";
    text.style.top = ((p_bounds.width * 0.5) - (bounds.height * 0.5)) + "px";
    text.style.left = ((p_bounds.width * 0.5) - (bounds.width * 0.5)) + "px";
    panel.appendChild(text);

    return panel;
}

//////////////////////////////////////////////////////////////////////////////////
function getBounds(p_text, p_options, p_debug) {
    var element = document.createElement('div'),
        bounds = {width: 0, height: 0};

    element.appendChild(document.createTextNode(p_text));

    p_options.fontFamily = p_options.fontFamily || "arial";
    p_options.fontSize = p_options.fontSize || "12px";
    p_options.fontWeight = p_options.fontWeight || "normal";

    element.style.fontFamily = p_options.fontFamily;
    element.style.fontWeight = p_options.fontWeight;
    element.style.fontSize = p_options.fontSize;
    element.style.position = 'absolute';
    element.style.visibility = p_debug ? 'visible' : 'hidden';
    element.style.left = p_debug ? "0px" : "-1000px";
    element.style.top = p_debug ? "0px" : "-1000px";
    element.style.width = 'auto';
    element.style.height = 'auto';

    if( p_debug ){
        element.style.background = "#FFFF00";
        element.style.color = "#000000";
    }

    document.body.appendChild(element);
    bounds.width = element.offsetWidth;
    bounds.height = element.offsetHeight;
    if( !p_debug ) document.body.removeChild(element);

    return bounds;
}

run();

Fiddle: https://jsfiddle.net/neoRiley/dgezn5wt/