2011/11/23

jQuery cssHook rotate3D

Los cssHooks de jQuery me parecen el no va más en simplificación y hacen un gran trabajo para hacer uniforme el comportamiento de los CSS a través de los distintos navegadores.

Para el que no sepa qué es un cssHook, le remito a la documentación de jQuery (http://api.jquery.com/jQuery.cssHooks/), pero en pocas palabras es la posibilidad de definir y usar propiedades CSS inventadas (o no) de modo que se puedan leer y ficjar sus valores, ejecutando un código concreto en este caso, claro.

Sirven para o crear nuevas propiedades CSS que hagan cosas maravillosas (este caso) o hacer compatibles distintos navegadores usando una misma propiedad CSS en todos ellos. P.e. con ellos se puede usar la propiedad border radius en IE6 y funcionará igual que en cualquier otro navegador.

Este cssHook, realiza una rotación isométrica de un elemento HTML. Se puede usar directamente mediante el método .css() de jQuery (tanto para hacer el set como para el get), así como parte de una llamada al método .animate() en todas sus variantes. El ángulo es en grados y se permiten tanto valores positivos como negativos así como expresiones de jQuery tipo '+=180deg', que lo que hará es sumarle 180 grados a la rotación actual.

Como un ejemplo es mejor que mil palabras, os pongo el jsfiddle del cssHook para que podáis probarlo en directo. Haced click en la carta para darle la vuelta. podía haber usado la imagen de una moneda, pero he preferido la de una carta.

Importante!
Dado que éste cssHook usa la propiedad CSS3 transform, para simplificar su uso sin tener que recurrir a transformaciones de matrices, usa a su vez otro cssHook que se encarga de hacer más sencilla la definición de la propiedad transform. En este caso, se ha usado el cssHook de brandonaaron/Louis Rémi pero hay otras alternativas aunque posiblemente, menos elegantes.

Extra!
Además del rotate3D, se define también otro cssHook llamado "flipped" para el control de cuándo se pasa de una cara a la otra al realizar la rotación.

Este cssHook, se encarga de mantener el estado de qué cara del elemento se está mostrando. Si la propiedad css 'flipped' es false, es la parte de adelante, si es false, la de atrás. El valor de la propiedad se cambia automáticamente cuando se gira el elemento al llegar a los 90 y a los 270 grados (módulo 360, como es lógico).

Asimismo, definiendo el método $.fn.flippedEvent, se consigue un callback en el momento que se produce el cambio de cara por ejemplo, para cambiar la imagen que se muestra.
$.fn.flippedEvent = function( elem, isFlipped ) { <yourCode> };

Y ahora, la versión minimizada de todo el código, que llega a unos ridículos 500bytes!!!
(function(b){if(!b.cssHooks)throw"jQuery 1.4.3 or above is required for this plugin to work";b.fn.flippedEvent=null;b.cssNumber.flipped=!0;b.cssHooks.flipped={get:function(a){return a.style.flipped||!1},set:function(a,d){var c=/true/i.test(d);"function"===typeof b.fn.flippedEvent&&b.fn.flippedEvent.flipEvent(a,c);a.style.flipped=c}};b.cssNumber.rotate3D=!0;b.cssHooks.rotate3D={get:function(a){return a.style.rotate3D},set:function(a,d){d=parseFloat(d);var c=d;if(c!=parseFloat(a.style.rotate3D)){var i=
0<=c?1:-1,f=Math.floor(Math.abs(c)),j=(180<=f%360?-1:1)*(1-f%180/90),g=b(a),e=parseInt(a.style.rotate3D),h=e<c?!0:!1,e=Math.abs(e)%180,c=Math.abs(parseInt(c))%180;(h&&90>e&&90<=c||!h&&90<e&&90>=c)&&g.css("flipped",!g.css("flipped"));g.css("transform","skew(0deg, "+i*f+"deg) scale("+j+", 1)")}a.style.rotate3D=d}};b.fx.step.rotate3D=function(a){b.cssHooks.rotate3D.set(a.elem,a.now,a.unit)}})(jQuery);

Código completo del cssHook. Como podréis ver, hace falta muy poco código para hacer que todo funcione.
/**
 * rotate3D/flipped jQuery cssHook v0.1
 * ==========================================================
 * (C) 2011 José Ramón Díaz - jrdiazweb@gmail.com
 *
 * Elegant and simple cssHooks to perform 3D-like css rotations and control 
 * visible side. 
 * 
 * REQUIRES.
 * A cssHook/code make transform CSS3 property work converting human readable 
 * values into its transformation matrix. In my case i've used the transform 
 * cssHooks from Louis-Rémi Babé to make it compatible, but you are free to use 
 * whatever you want meanwhile makes transform work.  
 *     https://github.com/brandonaaron/jquery-cssHooks
 *     https://github.com/louisremi/jquery.transform.js
 * 
 * CSS properties use the grammar:
 *     - "rotate3D: <n>[deg]"
 *     - "flipped: true|false"
 * 
 * When an element is rotated, flipped CSS property is set according the 
 * element orientation. Side changes occurs on 90/270 degrees.
 * 
 * Side change event callback can be used defining the function $.fn.flippedEvent. 
 *     $.fn.flippedEvent = function( elem, isFlipped ) { <yourcode> };
 * 
 * Examples:
 *     - elem.css('rotate3D')      - Gets current rotation
 *     - elem.css('rotate3D', 180) - Rotates element 180 degrees
 *     - elem.animate({ rotate3D, -180 }, 1000);    - Animates element from current rotation to -180 degrees
 *     - elem.animate({ rotate3D, "+=360" }, 1000); - Animates element adding 360 degrees to its current rotation
 *     - $('.c').animate({'rotate3D': '+=180'}, 2000, 'easeOutBounce') - Animates using easing function 'easeInOutBounce' defined in jQueryUI
 *
 * Browser copatibility.
 * Any modern CSS3 compatible browser.
 * 
 * NOTE: cssHooks needs jQuery v1.4.3 or greater.
 * 
 * Inspired by zachstronaut's rotate3Di
 *     http://www.zachstronaut.com/projects/rotate3di/
 * 
 * Legal stuff
 *     You are free to use this code, but you must give credit and/or keep header intact.
 *     Please, tell me if you find it useful. An email will be enough.
 *     If you enhance this code or correct a bug, please, tell me.
 */
(function ($) {
 // Check if cssHooks are supported
 if(!$.cssHooks) { throw( "jQuery 1.4.3 or above is required for this plugin to work" ); return; }

 ///////////////////////////////////////////////////////////////////////////
   // 'flipped' cssHook. Property has the format: "flipped: true|false"
   $.fn.flippedEvent = null; // Event function called when changing the side: "function( elem, isFlipped? ) {}"
 $.cssNumber['flipped'] = true; // Special units. None in this case
 $.cssHooks['flipped']  = {
  
  get      : function( elem, computed, extra ) { return elem.style['flipped'] || false; },
  set      : function( elem, value ) { 
      var val = /true/i.test(value); // Boolean conversion
      if( typeof $.fn.flippedEvent === "function" )
       $.fn.flippedEvent.flipEvent( elem, val );
      elem.style['flipped'] = val;  
     }
  
 };
        
 ///////////////////////////////////////////////////////////////////////////
 // 'rotate3D' csshook. Property has the format: "rotate3D: <n>deg|rad"
 $.cssNumber['rotate3D'] = true; // Special units support 'deg', 'rad', etc
 $.cssHooks['rotate3D']  = {
  
  get: function( elem, computed, extra ) { return elem.style['rotate3D']; },
  set: function( elem, value, unit ) {
      value = parseFloat( value );
      apply3DRotation( elem, value, unit );
      elem.style['rotate3D'] = value;
     }
        
 };

 // Animation step function for 'rotate3D' custom CSS property
 $.fx.step['rotate3D'] = function ( fx ) { $.cssHooks['rotate3D'].set( fx.elem, fx.now, fx.unit ); };

 // Performs 3D rotation on element
 var apply3DRotation = function( elem, value, unit ) {

  if( value == parseFloat( elem.style['rotate3D'] ) ) return; // No changes

  var dir   = value >= 0 ? 1 : -1;
  var deg   = Math.floor( Math.abs( value ) );
  var scale = ( ( deg % 360 ) >= 180 ? -1 : 1 ) * ( 1 - ( deg % 180 ) / 90 );

  var el  = $(elem); // Caches jQuery element to speed up execution

  var old = parseInt( elem.style['rotate3D'] );
  var inc = old < value ? true : false; 

  var before = Math.abs( old ) % 180;
  var after  = Math.abs( parseInt( value ) ) % 180;

  // Side change control
  if(  inc && before < 90  && after >= 90 || !inc && before > 90  && after <= 90 )
   el.css('flipped', !el.css('flipped') );

  // Performs CSS transform
  el.css( 'transform', 'skew(0deg, ' + dir * deg + 'deg) ' +
                 'scale(' + scale + ', 1)'
  );
 };

})(jQuery);

2011/11/15

Sesiones PHP a lo campestre III. No se puede hacer más lento.

Parafraseando al famoso mago (por si no lo conocéis es manco y hace trucos de cartas con una única mano), voy a darle la vuelta de tuerca definitiva a este método de obtener la información de sesión sin abrir la sesión.

Una vez que ya sabemos dónde están los ficheros de sesión tal y como se ha visto en las entregas anteriores, podemos simular de forma idéntica a como hace PHP la carga de la información de la sesión en una matriz asociativa.

El primer paso es cargar los contenidos del fichero de la sesión.
$fname    = session_save_path() . "/sess_" . $sid;
$_session = unserializeSession( file_get_contents( $fname ) );

Y para ello, se usa la siguiente función. Si es que es demasiado fácil...
///////////////////////////////////////////////////////////////////////////////
/**
 * unserializeSession - Unserialize para ficheros de sesión
 *
 * Convierte una cadena que se corresponde con los contenidos de un fichero de sesión en un array 
 * asociativo con las claves y valores de la sesión correspondiente.
 *
 * @access    public
 * @link      http://www.php.net/manual/en/function.session-decode.php#101687
 *
 * @param     string  $data  Cadena de texto del fichero de la sesión
 * @return    array          Array asociativa con las claves y valores
 */
function unserializeSession( &$data )
{
       if(  strlen( $data) == 0)
       {
              return array();
       }

       // match all the session keys and offsets
       preg_match_all('/(^|;|\})([a-zA-Z0-9_]+)\|/i', $data, $matchesarray, PREG_OFFSET_CAPTURE);

       $returnArray = array();

       $lastOffset = null;
       $currentKey = '';
       foreach ( $matchesarray[2] as $value )
       {
              $offset = $value[1];
              if(!is_null( $lastOffset))
              {
                     $valueText = substr($data, $lastOffset, $offset - $lastOffset );
                     $returnArray[$currentKey] = unserialize($valueText);
              }
              $currentKey = $value[0];

              $lastOffset = $offset + strlen( $currentKey )+1;
       }

       $valueText = substr($data, $lastOffset );
       $returnArray[$currentKey] = unserialize($valueText);

       return $returnArray;
}

2011/11/14

Colocando el cursor con javascript

Seguro que alguna vez hemos querido colocar el cursor de un cuadro de texto o un textarea para cualquier cosa que se nos haya ocurrido, la pega es que no hay métodos nativos para ello, pero jugando un poco con los rangos de selección, es fácil de hacer.

A continuación os pongo dos pequeñas funciones para conseguir el setter/getter de la posición del cursor. Es una "tontá", pero es efectiva. Además si lo minimizas ocupa una miseria.

function getCursorPosition( elem ) {

    var CaretPos = 0;
    // IE Support
    if( document.selection ) {

        elem.focus();
        var Sel = document.selection.createRange();

        Sel.moveStart( 'character', -elem.value.length );

        CaretPos = Sel.text.length;
    }
    // Firefox support
    else if ( elem.selectionStart || elem.selectionStart == '0' )
        CaretPos = elem.selectionStart;

    return( CaretPos );
}


function setCursorPosition( elem, pos )
{
    if( elem.setSelectionRange )
    {
        elem.focus();
        elem.setSelectionRange( pos,pos );
    }
    else if( elem.createTextRange ) {
        var range = elem.createTextRange();
        range.collapse( true );
        range.moveEnd( 'character', pos );
        range.moveStart( 'character', pos );
        range.select();
    }
}

2011/10/27

jQuery Canvas Minimap

Y para rematar la semana, la joya de la corona. Otro plugin hecho por mí que toma un contenedor y genera un minimapa de éste y que pese a basarse en otro ya existente llamado Fracs, le da un giro de tuerca y lo hace más flexible.

El principio de este plugin es tomar un contenedor del que se quiere hacer un minimapa e indicando un canvas de destino, pinta sobre él los elementos cuyos selectores hayamos definido usando el estilo que deseemos.

Usarlo es lo más sencillo del mundo. Dados un canvas #myCanvas y un contenedor
var minimapInstance = $('#myCanvas').minimap( $('myContainer') [, options] );

Las opciones son opcionales, pero recomendables pues es donde se definen qué elementos se representarán en el minimapa y cómo se hará. Concretamente, la encargada de ello es la propiedad "style" que  es un array de objetos con la siguiente estructura:
styleDef {
       // selector to use for that style
       selector: String / HTMLElement / [HTMLElement, ...] / jQuery

       // border stroke width
       strokeWidth: float / undefined / 'auto'

       // border color
       strokeStyle: String / undefined / 'auto'

       // fill color
       fillStyle: String / undefined / 'auto'
 }

Como una imagen vale más que mil palabras, como es usual, el repositorio del código está en mi jsFiddle. Solo hace falta el código javascript.

El código completo es éste
/**
 * jquery.minimap-0.1.js - Container Minimap Plugin
 * ==========================================================
 * (C) 2011 José Ramón Díaz - jrdiazweb@gmail.com
 *
 * http://3nibbles.blogspot.com/jquery-canvas-minimap.html
 * http://plugins.jquery.com/project/CanvasMinimap
 *
 * Container Minimap is a plugin that creates a minimap of the desired container
 * scaled down and styled so you can highlight the overall position of the
 * contents of the container element.
 *
 * Scale is calculated based on the minumum of width or height of the canvas used
 * to draw the minimap. So, using only CSS or attributes you can modify the scale
 * adjusting it to the desired size of the container.
 *
 * INSTANTIATION
 * Call the minimap method over the selector of the canvas where the minimap is
 * going to be drawn
 *
 *     minimapInstance = $('#myCanvas').minimap( $('myContainer') [, options] );
 *
 * The only HTML needed is the target canvas and the container to be drawn.
 *
 * OPTIONS
 *     - container : document.    Defaults to whole page. Otherwise is a
 *                                selector of the desired container to be mapped.
 *     - styles    : [ styleDef, ... ]    array with the styles to apply.
 *     - viewportStyle : styleDef. Style of the viewport.
 *     - viewportDragStyle: styleDef. Style of the viewport
 *
 * The OutlineStyle style must be in the following format (Same format than Fracs):
 *
 *     styleDef {
 *         // selector to use for that style
 *         selector: String / HTMLElement / [HTMLElement, ...] / jQuery
 *
 *         // border stroke width
 *         strokeWidth: float / undefined / 'auto'
 *
 *         // border color
 *         strokeStyle: String / undefined / 'auto'
 *
 *         // fill color
 *         fillStyle: String / undefined / 'auto'
 *     }
 *
 * strokeWidth, strokeColor, fillColor may have the special values
 * undefined to ignore and 'auto' to fetch values from css.
 *
 * PUBLIC API
 *     - $.minimap.redraw()    Redraws the minimap
 *     - drag                  Boolean that indicates that the viewport is being dragged
 *
 * Legal stuff
 *     Based on jQuery plugin named Fracs, but modified to allow minimapping of a
 *     desired container and some other enhancements.
 *
 *     You are free to use this code, but you must give credit and/or keep header intact.
 *     Please, tell me if you find it useful. An email will be enough.
 *     If you enhance this code or correct a bug, please, tell me.
 */
(function( $ ) {

 var $window   = $(window)
   , $document = $(document)
   ;

 ////////////////////////////////////////////////////////////////////////////////
    Rect = function (left, top, width, height) {

        if (!(this instanceof Rect)) {
            return new Rect(left, top, width, height);
        }

        this.left   = Math.round( left   );
        this.top    = Math.round( top    );
        this.width  = Math.round( width  );
        this.height = Math.round( height );
        this.right  = this.left + this.width;
        this.bottom = this.top  + this.height;
    };

    Rect.prototype = {
        equals: function( that ) {
            return this.left === that.left && this.top === that.top && this.width === that.width && this.height === that.height;
        },

        area: function() {
            return this.width * this.height;
        },

        intersection: function( rect ) {
            var left   = Math.max( this.left  , rect.left   ),
                right  = Math.min( this.right , rect.right  ),
                top    = Math.max( this.top   , rect.top    ),
                bottom = Math.min( this.bottom, rect.bottom ),
                width  = right  - left,
                height = bottom - top;

            return ( width >= 0 && height >= 0 ) ? Rect( left, top, width, height ) : undefined;
        },

        envelope: function( rect ) {
            var left   = Math.min( this.left  , rect.left   ),
                right  = Math.max( this.right , rect.right  ),
                top    = Math.min( this.top   , rect.top    ),
                bottom = Math.max( this.bottom, rect.bottom ),
                width  = right  - left,
                height = bottom - top;

            return Rect( left, top, width, height );
        }
    };


    /**
     * Special constructors
     */
    Rect.ofDocument = function() {
        return Rect( 0, 0, $document.width(), $document.height() );
    };

    Rect.ofViewport = function() {
        return Rect( $window.scrollLeft(), $window.scrollTop(), $window.width(), $window.height() );
    };

    Rect.ofElement = function( element ) {
        var $element = $(element)
          , offset;

        if( !$element.is(":visible") ) {
            return Rect( 0, 0, -1, 0 );
        }

        offset = $element.offset();
        return Rect( offset.left, offset.top, $element.outerWidth(), $element.outerHeight() );
    };

    Rect.ofElementOS = function( element, contOffset ) {
        var $element = $(element)
          , offset;

        if( typeof contOffset === 'undefined' || !contOffset ) contOffset = { left: 0, top: 0 };
        if( !$element.is(":visible") ) {
            return Rect( 0, 0, -1, 0 );
        }

        offset = $element.offset();
        return Rect( offset.left - contOffset.left, offset.top - contOffset.top, $element.outerWidth(), $element.outerHeight() );
    };


 ////////////////////////////////////////////////////////////////////////////////
    // Instantiation
    $.fn.minimap = function( container, options ) {

     var contOffset;

     // Private members
     function MiniMap( canvas, container, options ) {

         if ( !(this instanceof MiniMap) ) {
             return new MiniMap( canvas, container, options );
         }

         if ( !canvas.nodeName || canvas.nodeName.toLowerCase() !== "canvas" ) {
             return undefined;
         }

         var me = this
           , $canvas    = $(canvas)
           , $container = $(container) || document
           , width      = $canvas.width()  || $canvas.attr("width")
           , height     = $canvas.height() || $canvas.attr("height")
           , context    = canvas.getContext("2d")
           , drag       = false
           , vpRect
           , scale
           ;

         options = $.extend( {}, $.fn.minimap.defaults, options );

      // ========================================================================
      // Public Members
         this.redraw = function() {
          // Gets the container offset to transform contents position
          contOffset = $(container).offset();
          if( !contOffset ) contOffset = { left: 0, top: 0 };

          // Gets the Rect of the container
          docRec = null;
    if( $container === document )
     docRect = Rect.ofDocument();
    else
     docRect = Rect.ofElement( $container );
    vpRect = Rect.ofViewport();
    scale = Math.min(width / docRect.width, height / docRect.height);

    // Scales canvas on size and on inner transform
    $canvas.height( docRect.height * scale );
    $canvas.width(  docRect.width  * scale );

    context.setTransform( 1, 0, 0, 1, 0, 0 );
    context.clearRect( 0, 0, $canvas.width(), $canvas.height() );

    //context.scale( scale, scale ); // WRONG! deforms the projection
    context.scale( width / docRect.width, height / docRect.height );

    // Draws the minimap
    applyStyles();
    //drawViewport();
         },

         drawRect = function( rect, strokeWidth, strokeStyle, fillStyle, invert ) {
          // Draws a Rect on canvas
             if ( strokeStyle || fillStyle ) {
                 if (fillStyle) {
                     context.beginPath();
                     if (invert) {
                         context.rect( 0, 0, docRect.width, rect.top );
                         context.rect( 0, rect.top, rect.left, rect.height );
                         context.rect( rect.right, rect.top, docRect.right - rect.right, rect.height );
                         context.rect( 0, rect.bottom, docRect.width, docRect.bottom - rect.bottom );
                     } else {
                         context.rect( rect.left, rect.top, rect.width, rect.height );
                     }
                     context.fillStyle = fillStyle;
                     context.fill();
                 }
                 if (strokeStyle) {
                     context.beginPath();
                     context.rect( rect.left, rect.top, rect.width, rect.height );
                     context.lineWidth = scale ? Math.max( strokeWidth, 0.2 / scale ) : strokeWidth;
                     context.strokeStyle = strokeStyle;
                     context.stroke();
                 }
             }
         },

         drawElement = function( element, strokeWidth, strokeStyle, fillStyle ) {
          // Gets the Rect of an element and draws it on canvas
             var $element = $(element),
                 rect = Rect.ofElementOS( element, contOffset );
              //rect = Rect.ofElement( element );

             if ($element.css("visibility") === "hidden" || rect.width === 0 || rect.height === 0) {
                 return;
             }

             strokeWidth = strokeWidth === "auto" ? parseInt($element.css("border-top-width"), 10) : strokeWidth;
             strokeStyle = strokeStyle === "auto" ? $element.css("border-top-color") : strokeStyle;
             fillStyle   = fillStyle   === "auto" ? $element.css("background-color") : fillStyle;
             drawRect(rect, strokeWidth, strokeStyle, fillStyle);
         },

         applyStyles = function () {
          // Loops through the contents of the container
             $.each( options.styles, function (idx, style) {
                 $(style.selector).each(function () {
                     drawElement( this, style.strokeWidth, style.strokeStyle, style.fillStyle );
                 });
             });
         },

         drawViewport = function () {
             var style = drag && options.viewportDragStyle ? options.viewportDragStyle : options.viewportStyle;

             drawRect( vpRect, style.strokeWidth, style.strokeStyle, style.fillStyle, options.invertViewport );
         };

         me.redraw();
     }

        return new MiniMap( this.get(0), container, options );
    };

    // ========================================================================
    // Minimap options defaults
    $.fn.minimap.defaults = {
        container  : document,        // Defaults to whole page. Otherwise is a selector of the container
        styles     : [{               // Sample styles
                      selector: "header,footer,section,article",
                      fillStyle: "rgb(230,230,230)"
                     }, {
                      selector: "h1",
                      fillStyle: "rgb(240,140,060)"
                     }, {
                      selector: "h2",
                      fillStyle: "rgb(200,100,100)"
                     }, {
                      selector: "h3",
                      fillStyle: "rgb(100,200,100)"
                     }, {
                      selector: "h4",
                      fillStyle: "rgb(100,100,200)"
                     }]
    };

}(jQuery));

Y el código minimizado que llega a ocupar menos de 1.5Kb es
/** !jquery.minimap-0.1.js - Container Minimap Plugin
 * (C) 2011 José Ramón Díaz - jrdiazweb@gmail.com
 * http://3nibbles.blogspot.com/jquery-canvas-minimap.html | http://plugins.jquery.com/project/CanvasMinimap
 *
 * Based on jQuery plugin named Fracs, but modified to allow minimapping of a desired container and some other enhancements.
 * You are free to use this code, but you must give credit and/or keep header intact.
 */
(function(f){var h=f(window),j=f(document);Rect=function(a,b,c,e){if(!(this instanceof Rect))return new Rect(a,b,c,e);this.left=Math.round(a);this.top=Math.round(b);this.width=Math.round(c);this.height=Math.round(e);this.right=this.left+this.width;this.bottom=this.top+this.height};Rect.prototype={equals:function(a){return this.left===a.left&&this.top===a.top&&this.width===a.width&&this.height===a.height},area:function(){return this.width*this.height},intersection:function(a){var b=Math.max(this.left,
a.left),c=Math.min(this.right,a.right),e=Math.max(this.top,a.top),a=Math.min(this.bottom,a.bottom);c-=b;a-=e;return c>=0&&a>=0?Rect(b,e,c,a):void 0},envelope:function(a){var b=Math.min(this.left,a.left),c=Math.max(this.right,a.right),e=Math.min(this.top,a.top),a=Math.max(this.bottom,a.bottom);return Rect(b,e,c-b,a-e)}};Rect.ofDocument=function(){return Rect(0,0,j.width(),j.height())};Rect.ofViewport=function(){return Rect(h.scrollLeft(),h.scrollTop(),h.width(),h.height())};Rect.ofElement=function(a){var a=
f(a),b;if(!a.is(":visible"))return Rect(0,0,-1,0);b=a.offset();return Rect(b.left,b.top,a.outerWidth(),a.outerHeight())};Rect.ofElementOS=function(a,b){var c=f(a),e;if(typeof b==="undefined"||!b)b={left:0,top:0};if(!c.is(":visible"))return Rect(0,0,-1,0);e=c.offset();return Rect(e.left-b.left,e.top-b.top,c.outerWidth(),c.outerHeight())};f.fn.minimap=function(a,b){function c(a,b,i){if(!(this instanceof c))return new c(a,b,i);if(a.nodeName&&a.nodeName.toLowerCase()==="canvas"){var g=f(a),h=f(b)||document,
j=g.width()||g.attr("width"),l=g.height()||g.attr("height"),d=a.getContext("2d"),m,k,i=f.extend({},f.fn.minimap.defaults,i);this.redraw=function(){(e=f(b).offset())||(e={left:0,top:0});docRec=null;docRect=h===document?Rect.ofDocument():Rect.ofElement(h);m=Rect.ofViewport();k=Math.min(j/docRect.width,l/docRect.height);g.height(docRect.height*k);g.width(docRect.width*k);d.setTransform(1,0,0,1,0,0);d.clearRect(0,0,g.width(),g.height());d.scale(j/docRect.width,l/docRect.height);applyStyles()};drawRect=
function(a,b,c,e,f){if(c||e){if(e)d.beginPath(),f?(d.rect(0,0,docRect.width,a.top),d.rect(0,a.top,a.left,a.height),d.rect(a.right,a.top,docRect.right-a.right,a.height),d.rect(0,a.bottom,docRect.width,docRect.bottom-a.bottom)):d.rect(a.left,a.top,a.width,a.height),d.fillStyle=e,d.fill();if(c)d.beginPath(),d.rect(a.left,a.top,a.width,a.height),d.lineWidth=k?Math.max(b,0.2/k):b,d.strokeStyle=c,d.stroke()}};drawElement=function(a,b,c,d){var g=f(a),a=Rect.ofElementOS(a,e);g.css("visibility")==="hidden"||
a.width===0||a.height===0||(b=b==="auto"?parseInt(g.css("border-top-width"),10):b,c=c==="auto"?g.css("border-top-color"):c,d=d==="auto"?g.css("background-color"):d,drawRect(a,b,c,d))};applyStyles=function(){f.each(i.styles,function(a,b){f(b.selector).each(function(){drawElement(this,b.strokeWidth,b.strokeStyle,b.fillStyle)})})};drawViewport=function(){var a=i.viewportStyle;drawRect(m,a.strokeWidth,a.strokeStyle,a.fillStyle,i.invertViewport)};this.redraw()}}var e;return new c(this.get(0),a,b)};f.fn.minimap.defaults=
{container:document,styles:[{selector:"header,footer,section,article",fillStyle:"rgb(230,230,230)"},{selector:"h1",fillStyle:"rgb(240,140,060)"},{selector:"h2",fillStyle:"rgb(200,100,100)"},{selector:"h3",fillStyle:"rgb(100,200,100)"},{selector:"h4",fillStyle:"rgb(100,100,200)"}]}})(jQuery);

Éste ha quedado chulo, ein?

No significa nada, pero me hace ilu!

Mi plugin de WizardSteps está el número 3 de la lista de los 10 plugins de navegación más creativos según script-tutorials.com

http://www.script-tutorials.com/10-most-creative-jquery-navigation-plugins/

Y también se me nombra en otros sitios en posts con el apelativo de "superb" :D

http://www.multyshades.com/2011/08/30-superb-jquery-plugins-for-dropdown-navigation-menus/

Ya se que no es un ranking mundial ni nada de eso, pero siempre es agradable algo de reconocimiento, no?