Another Reflector – blazingly fast

As you may have noticed, even the improved version of the scriptaculous Reflector is too slow to be used on pages with more than just a few small images.

So I took the opportunity to look at a Javascript subject that so far had escaped my studies, that is, the Safari/Gecko canvas tag. The canvas tag offers powerful drawing capabilities which are performed right in the browser, have a look at some of it’s capabilities.
Of course, IE goes a different way and does not support canvas. Still, by applying its CSS filter attribute one can get a similar reflection effect.

While I was working on the new version and already had a first working version I came across the nice reflection.js by Cow, which does exactly what I was trying to get. Exactly the same way I was trying to do it. And it worked. I was too late.
I thus scrapped my code and took reflection.js, modified it a bit and fixed some bugs. Here it is, have a look at the demo page.
Compared to the scriptaculous version, this code seems longer and of course less elegant. But it is still pretty straightforward, possibly except for the scalings and offsets. You also have to keep in mind that there are actually two routines in it, for both Safari/Gecko and IE.
Since both the canvas tag and the IE CSS filter only support linear gradients, the fade option introduced in my “faster reflections” version had to be dropped, sorry.

var Reflector = {

  reflect: function(element) {
    element = $(element);
    options = $H({
      amount: 1/3,
      opacity: 1/3
    }).merge(arguments[1] || {});

    // ******************************************************
    // the following code is based on "reflection.js" v1.0 by
    //           Cow http://cow.neondragon.net
    //           Gfx http://www.jroller.com/page/gfx/
    //           Sitharus http://www.sitharus.com
    //           Andreas Linde http://www.andreaslinde.de/
    //
    // modifications & fixes by Julian Dreissig
    // ******************************************************

    // compute size of reflected and total image size

    var reflectionWidth = element.width;
    var reflectionHeight = options.amount * element.height;
    var divHeight = (options.amount) * element.height;

    // replace image element by div containing the original image plus the reflection

    var d = Builder.node('div', {
      style: 'width:'+reflectionWidth+'px;'
           + 'height:'+divHeight+'px;'
    });
    element.parentNode.replaceChild(d, element);
    d.appendChild(element);

    // create the reflection

    if (document.all && !window.opera) {

      // Internet Exploder: apply MS filter via CSS

      var reflection = Builder.node('div', {
        style: 'width:'+reflectionWidth+'px; '
             + 'height:'+reflectionHeight+'px; '
             + 'background:url('+element.src+') 0 -'+(element.height-reflectionHeight)+'px; '
             + 'filter:flipv progid:DXImageTransform.Microsoft.Alpha(opacity='+(options.opacity*100)+', style=1, finishOpacity=0, startx=0, starty=0, finishx=0, finishy='+element.height+');'
      });
      d.appendChild(reflection);

    } else {

      // Intelligent browsers (FF, Safari...): create canvas element

      var canvas = Builder.node('canvas', {
        style: 'width:'+reflectionWidth+'px; '
             + 'height:'+reflectionHeight+'px;',
        width: reflectionWidth,
        height: reflectionHeight
      });
      d.appendChild(canvas);

      var context = canvas.getContext("2d");
      context.save();

      // Firefox needs some offset tweaking
      var ffOffset = /WebKit/.test(navigator.appVersion) ? 0 : -1;

      // mirror and draw image
      context.translate(0,reflectionHeight*2+ffOffset);
      context.scale(1,-1);  // mirroring along x-axis
      context.drawImage(element, 0, element.height-reflectionHeight, reflectionWidth, reflectionHeight, 0, reflectionHeight, reflectionWidth, reflectionHeight);

      // create fading gradient
      var gradient = context.createLinearGradient(0, reflectionHeight, 0, 2*reflectionHeight);
      gradient.addColorStop(0, "rgba(255, 255, 255, 1)"); // black
      gradient.addColorStop(1, "rgba(255, 255, 255, "+(1-options.opacity)+")");
      context.globalCompositeOperation = "destination-out";
      context.fillStyle = gradient;

      if (/WebKit/.test(navigator.appVersion))
        context.fill();
      else
        context.fillRect(0, reflectionHeight+ffOffset, reflectionWidth, 2*reflectionHeight);

      context.restore();
    }
  }
}

Again, feel free to use the code as you like and/or drop me a comment. Please note that reflection.js is published under a MIT-style license, which I thereby adopt for my code: MIT-licensed it is.

Yes, in the meantime reflection.js has evolved to version 1.5, fixing the bugs I also had fixed and more. So my code is not even worth a bug report. I really have some bad timing…


About this entry