HTML5 / CSS3 - Dynamic Shadows

Whilst perusing images of Asian interior decorating I became enthralled with the interplay of light and shadow in one particular photo of a bathroom with gold tiling. I've yet to achieve the exact effect with HTML but I did manage to produce the much simpler shadows. You can see the effect here. I've only tested the page on Chrome for Windows or Android. No guarantees if you're using another browser or operating system.

If you're not able to see the effect then the idea is that an imaginary light source, in this case the mouse, moves across the page and accordingly changes the shadows cast by various elements. Elements nearby will cast sharp and dark shadows. Elements far away will cast wide and lighter shadows. The positions of the shadows also differ depending on their relative position to the light source.

Although the effect is simple, I believe that placed within the context of a more dynamic and artistic page, it has the potential to add a nice touch of realism. The current trend is to build sparse layered pages in line with the philosophies of Material Design and whilst there are plenty of dynamic aspects to it, not many of those are applied to the associated shadows or lighting.

The Code

The mathematics behind the effect are fairly simple if you haven't guessed already. It begins by considering a vector from the imaginary light source to the center of the "div" whose shadow we will be setting. We then create a unit vector that we use to set the position of the shadow and use the magnitude (length) of the light source vector to determine the blur, darkness, and distance (see figure). Whilst you can download all the associated code from the demo page, I've also included the main listing at the end of this article.

Going Further

In this example, I've only applied the effect with the text-shadow and box-shadow CSS mechanisms but I can't foresee any reason why we couldn't also apply it to "canvas" shadows. Additionally we could combine shadows from multiple light sources and even change their colour or intensity. I've tested the effect on an old and new Nexus 7 tablet, a Nexus 4 phone, and a very slow Celeron M laptop and all were able to display the effect on the above mentioned page, quite smoothly.

And on an associated but much more advanced vein, there's a great implementation of lighting by Jonas Wegner, titled "Normal Mapping with Javascript and Canvas". My next goal is to apply a very coarse "normal map" to selective elements on a page coupled with animated light sources and the shadow effect described above. The trick will be to make it simple and fast enough that it can be integrated with the look and feel of an entire website.

The Code - For Real this Time

The core of the work occurs in the "Set_Shadow" function (Lines 47-70). My user of vector style calculations was merely to keep me sane.

1:  <!DOCTYPE html>  
2:  <html>  
3:    
4:   <head>  
5:    <title>Ramirez Systems - Blog - Dynamic Shadows</title>  
6:    <link href="main.css" rel="stylesheet"/>  
7:    <script src="jquery.js"></script>  
8:    <script>  
9:     function On_Load()  
10:     {  
11:      $(document).on("touchstart", On_Touch_Start);  
12:      $(document).on("touchmove", On_Touch_Start);  
13:      $(document).on("mousemove", On_Mouse);  
14:     }  
15:          
16:     function On_Mouse(e)  
17:     {  
18:      var light_pos,  
19:       box_ids=["d1", "d2", "d3", "d4", "d6", "d7", "d8", "d9"],   
20:       text_ids=["d5"];  
21:        
22:      light_pos=  
23:      {  
24:       "x": e.originalEvent.clientX,  
25:       "y": e.originalEvent.clientY  
26:      };  
27:      $.each(box_ids, function (){ Set_Shadow(this, light_pos, "box"); });  
28:      $.each(text_ids, function (){ Set_Shadow(this, light_pos, "text"); });  
29:     }  
30:    
31:     function On_Touch_Start(e)  
32:     {  
33:      var light_pos,  
34:       box_ids=["d1", "d2", "d3", "d4", "d6", "d7", "d8", "d9"],   
35:       text_ids=["d5"];  
36:        
37:      e.preventDefault();  
38:      light_pos=  
39:      {  
40:       "x": e.originalEvent.touches[0].pageX,  
41:       "y": e.originalEvent.touches[0].pageY  
42:      };  
43:      $.each(box_ids, function (){ Set_Shadow(this, light_pos, "box"); });  
44:      $.each(text_ids, function (){ Set_Shadow(this, light_pos, "text"); });  
45:     }  
46:       
47:     function Set_Shadow(id, light_pos, type)   
48:     {  
49:      var div_pos, div, light_dir, light_vec, light_dis,   
50:       shadow_dir, shadow_size, shadow_drk, css;  
51:        
52:      div=$("#"+id);  
53:      div_pos=  
54:      {  
55:       "x": div.offset().left+div.width()/2,  
56:       "y": div.offset().top+div.height()/2  
57:      };  
58:        
59:      light_vec=Sub_Vector(div_pos, light_pos);  
60:      light_dir=Unit_Vector(light_vec);  
61:      light_dis=Vector_Mag(light_vec);  
62:        
63:      shadow_dir=Mult_Vector(light_dir, light_dis/15);  
64:      shadow_size=light_dis/30;  
65:      shadow_drk=1/(1+light_dis/600);  
66:        
67:      css=""+shadow_dir.x+"px "+shadow_dir.y+"px "+  
68:       shadow_size+"px rgba(0,0,0,"+shadow_drk+")";  
69:      div.css(type+"-shadow", css);  
70:     }  
71:       
72:     function Unit_Vector(v)  
73:     {  
74:      return Div_Vector(v, Vector_Mag(v));  
75:     }  
76:       
77:     function Vector_Mag(v)  
78:     {  
79:      return Math.sqrt(v.x*v.x+v.y*v.y);  
80:     }  
81:       
82:     function Sub_Vector(u, v)  
83:     {  
84:      return {"x": u.x-v.x, "y": u.y-v.y};  
85:     }  
86:       
87:     function Mult_Vector(v, s)  
88:     {  
89:    
90:      return {"x": v.x*s, "y": v.y*s};  
91:     }  
92:       
93:     function Div_Vector(v, s)  
94:     {  
95:      return {"x": v.x/s, "y": v.y/s};  
96:     }  
97:     </script>  
98:   </head>  
99:    
100:   <body onload="On_Load()">  
101:    <img src="p1.JPG" id="d1">  
102:    <img src="p2.jpg" id="d2">  
103:    <img src="p3.jpg" id="d3">  
104:    <img src="p4.JPG" id="d4">  
105:    <div id="d5">Drag your finger or mouse across the page to change the location of  
106:     the light source.</div>  
107:    <img src="p5.JPG" id="d6">  
108:    <img src="p6.JPG" id="d7">  
109:    <img src="p7.JPG" id="d8">  
110:    <img src="p8.JPG" id="d9">  
111:   </body>  
112:    
113:  </html>  

Popular Posts