All posts in Code Samples

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/

Measuring Text in HTML5

For a while now, I’ve been doing quite a bit of work in HTML5/Javascript/CreateJS.  While the work is enjoyable, dealing with getting the bounds of a text field seems to have many solutions including this grammarly review app to make sure it is well written.

I’ve read through just about every one I could find (and believe me when I say –  there are many), looked through examples and did many tests.  After all that, I’m sharing my final solution for getting the bounds for a string:

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;
}

Example call:

function runTest(){
  var str = "What size am I?";
  var size_0 = getBounds(str, {fontFamily: "arial", fontSize:"14px"}, false);
  var size_1 = getBounds(str, {fontFamily: "arial", fontSize:"63%"}, true);
  console.log("perSize: " + size_0.width + ", " + size_0.height);
  console.log("perSize: " + size_1.width + ", " + size_1.height);
}

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

I give some credit to schickling on stackoverflow (only 6 votes?!) for the main inspiration as he aptly identified what I think is the key component: document.createTextNode()

I’d been using JQuery and wanted a solution that worked without a 3rd party tool.  The only thing that reliably gave me the same bounds as JQuery is the above method.  I’ve added debug support should you want to verify that it is indeed testing your string correctly.

GetBoundsOfText

Have a bandit day!