Dan Abramov
Dan Abramov

Reputation: 268265

Calculating realistic box-shadow with JavaScript

I'm building a book library webapp with books draggable between the shelves. 

Applying box-shadow to books made them look slightly more realistic but I wondered if I can go further and dynamically calculate box-shadow values for every book by its position relative to a single chosen “light source” point, like a game engine would do. 

Thus, if we choose top center point to be the light source, a book on the left will have a left bottom shadow, and a book on the right will have a right bottom shadow. Books at the bottom would get shadows of larger height, et cetera. We might need to specify depth (z-coordinate) for the light source as well as its position.

While box-shadow doesn't allow for complex realistic shadows, I suspect it would be more than enough to adjust shadow size and angle with regards to their position for rectangular objects such as books to make them a lot more realistic. 

Has anyone already looked into implementing this in JavaScript? Are you aware of any open source libraries that calculate box-shadow values with regards to a specific light source point? If not, is the idea inherently wrong in some way I haven't thought of, or is it that nobody has tried just this yet?

Upvotes: 3

Views: 1112

Answers (2)

user1150525
user1150525

Reputation:

I wrote my try in a comment, but maybe I'll get upvotes so I'll write it in a answer, too. :D .

var source = [0, 0], faktor = [10, 20];

The source is where the light should be, the faktor is the faktor for the shadows ([0] for shadow position, [1] for blur).

function addShadows() {
    var i, j, position, sizeE, distance; for(i=0,j=arguments.length;i<j;++i) {
        position = offset(arguments[i]); // Get position from element

        sizeE = size(arguments[i]); // Get width and height from element

        distance = parseInt(Math.sqrt(Math.pow(position[0] + sizeE[0] / 2 - source[0], 2) + Math.pow(position[1] + sizeE[1] / 2 - source[1], 2))) / faktor[1]; // calculate a distance for bluring (middle of element to source)

        arguments[i].style.cssText += 'box-shadow: ' + ((position[0] + sizeE[0] - source[0]) / faktor[0]) + 'px ' + ((position[1] + sizeE[1] - source[1]) / faktor[0]) + 'px ' + distance + 'px #555555'; // add the shadow
    }
}

The function addShadows will add shadows to all parameters.

function getStyle(element, attribut) {
    var style;

    if(window.getComputedStyle) {
        style = window.getComputedStyle(element, null);
    } else {
        style = element.currentStyle;
    }

    return style[attribut];
}
function offset(element) {
    var pos = [parseInt(getStyle(element, 'border-top')), parseInt(getStyle(element, 'border-top'))];

    while(element !== null) {
        pos[0] += element.offsetLeft;
        pos[1] += element.offsetTop;

        element = element.offsetParent;
    }

    return pos;
}
function size(element) {
    return [element.offsetWidth, element.offsetHeight];
}
function id(idE) {
    return document.getElementById(idE);
}

The four functions above are just helper functions.

JSfiddle: http://jsfiddle.net/R5UbL/1/

Upvotes: 1

Vincent Briglia
Vincent Briglia

Reputation: 3068

A bloke called Jeff Pamer made an experiment about this

requires jQuery and jQuery UI Draggable (see demo)

your html:

<div id="light_source">Light Source<br />(drag me)</div>
<div id="div1" class="needs_shadow">(drag me)</div>
<div id="div2" class="needs_shadow">(drag me)</div>

your JavaScript

$(document).ready(function() {
  $("div").draggable({
    drag: function() { make_shade() }
  });
  make_shade();  
});

function make_shade() {
  var light = $("div#light_source");
  var light_pos = light.position();
  var light_x = light_pos.left + (light.width() / 2);
  var light_y = light_pos.top + (light.height() / 2);

  $(".needs_shadow").each(function() {
    var div1 = $(this);
    var div1_pos = div1.position();

    var div_x = div1_pos.left + (div1.width() / 2);
    var div_y = div1_pos.top + (div1.height() / 2);

    var left_diff = light_x - div_x;
    var top_diff = light_y - div_y;

    var left = (left_diff / 10) * -1;
    var top = (top_diff / 10) * -1;

    var distance = Math.sqrt(Math.pow(left_diff, 2) + Math.pow(top_diff, 2));
    distance = distance / 10;

    shadow_style = left + "px " + top + "px " + distance + "px #3f3f3f";
    div1.css("-moz-box-shadow", shadow_style);
    div1.css("-webkit-box-shadow", shadow_style);
    div1.css("box-shadow", shadow_style);
  });
}

Upvotes: 4

Related Questions