Mostrando entradas con la etiqueta CSS3. Mostrar todas las entradas
Mostrando entradas con la etiqueta CSS3. Mostrar todas las entradas

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/10/26

Plugin jQuery Sliding Panel

Hola, hoy os presento uno de los plugins de jQuery que he hecho y que os he prometido para esta semana. Hablamos de la continuación del Slide Button ya que comparte gran parte de la apariencia y funcionalidad, pero aplicado a paneles en vez de a botones.


Este plugin está basado en uno denominado jqEasy (un saludo!), pero con cuyas tripas no estoy de acuerdo, ya que arrastraba varios fallos, no seguía las recomendaciones de desarrollo de plugins, definiciones CSS incompletas y memory leaks graves, así que opté por reescribirlo todo desde cero, tanto el CSS como javascript adaptándolo a la apariencia de los slide buttons (lo cual no quiere decir que mi código sea infalible, solo que los errores que detecté han sido corregidos).

Usarlo es lo más sencillo del mundo, ya que solo hay que llamar a la función slidePanel() sobre el contenedor del widget, que tiene que tener una mínima estructura que os detallaré más adelante. La única particularidad es que si se quiere a la izquierda no hay que hacer nada, pero si se quiere a la derecha hay que anadir la clase ".right" al contendor (o usar las propiedades del plugin).

Por defecto también considera que está "fixed" a la página, pero cambiando la propiedad position del CSS (o del objeto jQuery) a "relative" o "absolute", se cambia el comportamiento del widget radicalmete. Para más detalles podéis comprobar el ejemplo del jsFiddle, donde hay ejemplos con las 6 combinaciones posibles.

También es fácilmente integrable con jQueryUI (los estilos aún no, lo siento), pudiéndose usar dentro de una pila de llamadas para añadirle comportamiento con draggable() o modificando las funciones de despliegue del panel mediante las propiedades del objeto jQuery.

Debe funcionar en todos los navegadores, incluido el IE6 (aunque no soporta la propiedad CSS "position: fixed" sustituyéndose por "position: absolute" de forma automática). Evidentemente, mientras más "moderno" sea el navegador, más bonito se verá (transparencias, esquinas redondeadas, etc), pero eso es cosa de CSS y no de código.

Sin más demoras, os dejo el jsF


iddle, donde está todo el código, css y ejemplos necesarios. El código está comentado así como documentado así que no deberíais tener mucho problema. Si encontrarais un bug, no dudéis en comunicármelo. Alternativamente, podéis visitar la página del plugin en la página oficial de jQuery.


Y para los más impacientes, el código completo:

Estructura básica HTML
<div class="slidePanelWidget" id="widget1">
    <a class="slidePanelButton" href="">&nbsp;Widget 1</a>
    <div class="slidePanel panel">

        These are the contents of the widget!
    </div>
</div>

Código javascript minimizado. Llega a ocupar un mínimo de unos 900 bytes. Quién pide más?
/*! jquery.slidePanel-0.1.js - Slide Panel plugin for jQuery
* ==========================================================
* (C) 2011 José Ramón Díaz - jrdiazweb@gmail.com - v.0.1.0 (25/10/2010)
* Requires: jQuery v1.4.3+
* http://3nibbles.blogspot.com/2011/10/plugin-jquery-sliding-panel.html
* 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(b){function f(c,d){var g=b.browser.msie&&b.browser.version=="6.0",a=this;a.opts=d;a.widget=c;a.button=a.widget.find(".slidePanelButton");a.panel=a.widget.find(".slidePanel");a.left=a.widget.hasClass("right")?false:true;if(g)a.opts.position=a.opts.position=="fixed"?"absolute":a.opts.position;a.opts.position&&a.button.css("position",a.opts.position);a.opts.buttonTop&&a.button.css("top",a.opts.buttonTop);a.opts.panelTop&&a.panel.css("top",a.opts.panelTop);a.button.attr("href","javascript:void(0)").click(function(){a.opts.ajax?
a.panel.is(":visible")?a.toggle():a.panel.load(a.opts.ajax,function(b,c){c!=="success"&&a.panel.html("Sorry, but there was an error loading the document.

");a.toggle()}):a.toggle();return false});a.deployed=false;a.toggle=function(b){if(typeof open==="boolean")a.deployed=b;a.deployed?a.close():a.open()};var e=0;a.open=function(){a.deployed=true;var b=a.button.width();b&&(oldPaddingR=a.button.css("padding-right"),oldPaddingL=a.button.css("padding-left"),e=b);a.button.addClass("deployed");a.left?
a.button.animate({width:"0px","padding-right":"0px"},a.opts.speed,a.opts.animFunction):a.button.animate({width:"0px","padding-right":"0px","padding-left":oldPaddingR},a.opts.speed,a.opts.animFunction);a.panel.toggle(a.opts.speed,a.opts.animFunction,function(){});a.opts.clickToClose&&a.enableCloseFn()};a.close=function(){a.deployed=false;a.panel.toggle(a.opts.speed,a.opts.animFunction,function(){});a.left?a.button.animate({width:e+"px","padding-right":oldPaddingR},a.opts.speed,a.opts.animFunction):
a.button.animate({width:e+"px","padding-right":oldPaddingR,"padding-left":oldPaddingL},a.opts.speed,a.opts.animFunction);a.button.removeClass("deployed");a.opts.clickToClose&&a.disableCloseFn()};a.stopCloseFn=function(a){a.stopPropagation()};a.enableCloseFn=function(){b(document).bind("click",a.close);a.panel.bind("click",a.stopCloseFn)};a.disableCloseFn=function(){b(document).unbind("click",a.close);a.panel.unbind("click",a.stopCloseFn)}}b.fn.slidePanel=function(c){var d=b.extend({},{position:null,
buttonTop:null,panelTop:null,animFunction:"swing",speed:"fast",ajax:null,clickToClose:true},c);return!this.data("slidePanel")||typeof c=="object"?this.each(function(){var c=b(this);c.data("slidePanel",new f(c,d))}):this.data("slidePanel")}})(jQuery);

Código javascript completo
/*! jquery.slidePanel-0.1.js - Slide Panel plugin for jQuery
 * ==========================================================
 * (C) 2011 José Ramón Díaz - jrdiazweb@gmail.com
 * Version: 0.1.0 (25/10/2010)
 * Requires: jQuery v1.4.3+
 *
 * http://3nibbles.blogspot.com/2011/10/plugin-jquery-sliding-panel.html
 *
 * INSTANTIATION
 * The basic structure is a div indicating on his class if it must be at
 * left (default, no extra class needed) or right (".right" class needed).
 * Inside the div there must be an <a class="slidePanelButton"> and a
 * <div class="slidePanel">. That's all.
 *
 * Manual instantiation
 *     $('.slidePanelWidget').slidePanel( [ options_object ] );
 *
 * Optionally if you use jQueryUI, you can add draggable to it with
 *     $('.slidePanelWidget').slidePanel( [ options_object ] ).draggable( { axis: 'y' } );
 *
 * OPTIONS
 *     - position     : null null (CSS position) / 'fixed' / 'absolute'
 *     - buttonTop    : null top position of the button inside slidePanelWidget. Null = CSS specified.
 *     - panelTop     : null top position of the panel inside slidePanelWidget. Null = CSS specified.
 *     - animFunction : 'swing' Animation function used to deploy panel
 *     - speed        : 'fast' Animation speed
 *     - ajax         : null URL of th AJAX call to perform to load panel contents. It is loaded before deploying the panel.
 *     - clickToClose : true True = click anywhere to close the panel
 *
 * API
 *     - toggle()  Toggles open/close panel state
 *     - open()    Forces open panel
 *     - close()   Forces close panel
 *     - deployed  Indicates that the control is deployed (opened)
 *
 * CLASSES USED
 *     - Container: .slidePanelWidget, .right
 *     - Button:    .slidePanelButton, .deployed
 *     - Content:   .slidePanel
 *
 * HTML format
 *     <div id="widget1" class="slidePanelWidget">
 *        <a class="slidePanelButton"> Name</a>
 *        <div class="slidePanel panel">
 *
 *            <!-- Widget contents -->
 *            Paste here the contents of the panel
 *            <!-- End of widget contents -->
 *
 *        </div>
 *      </div>
 *
 * 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.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * Modifications:
 *     - 2011-10-25  JRDiaz  Creation
 */
(function($){

    // Instantiation
 $.fn.slidePanel = function( options ) {
  var defaults = {
     position     : null
   , buttonTop    : null
   , panelTop     : null
   , animFunction : 'swing'
   , speed        : 'fast'
   , ajax         : null
   , clickToClose : true
  };

  var opts = $.extend( {}, defaults, options );

  if( !this.data('slidePanel') || typeof options == 'object' )
  {
   // Not instantiated or part of a selector call
   return this.each(function() {
    var me = $(this);
    me.data('slidePanel', new SlidePanel( me, opts ));
   });
  }
  else
  {
   return this.data('slidePanel');
  }
    };

 // SlidePanel Object
    function SlidePanel( el, opts ) {
  var isIE6  = $.browser.msie && $.browser.version == "6.0";

  var me    = this;
  me.opts   = opts;
  me.widget = el;
  me.button = me.widget.find('.slidePanelButton');
  me.panel  = me.widget.find('.slidePanel');
  me.left   = me.widget.hasClass('right') ? false : true;

  // ie6 doesn't like fixed position
  if( isIE6 ) { me.opts.position = me.opts.position == 'fixed' ? 'absolute' : me.opts.position; }

  // set css properties for button and panel
  if( me.opts.position  ) me.button.css( 'position', me.opts.position );
  if( me.opts.buttonTop ) me.button.css( 'top', me.opts.buttonTop );
  if( me.opts.panelTop  ) me.panel.css ( 'top', me.opts.panelTop  );

  // Button click event
  //me.button.attr( "href", "javascript:void(0)" ).mousedown(function() {
  me.button.attr( "href", "javascript:void(0)" ).click( function() {
   // load default content if ajax is false
   if (!me.opts.ajax) {
    //me.panel.toggle( me.opts.speed, me.opts.animFunction, function () {} );
    me.toggle();
   }
   else
   {
    // Loads the panel contents via AJAX if opts.ajax is defined
    if ( !me.panel.is(':visible') ) { // fetch data ONLY when panel is hidden...
     me.panel.load(me.opts.ajax, function( response, status, xhr ) {
      if (status !== "success") { // the ajax source wasn't loaded properly
       var msg = "<p>Sorry, but there was an error loading the document.</p>";
       me.panel.html(msg);
      };
      // Sets the HTML of the panel BEFORE opening it
      //me.panel.toggle( me.opts.speed, me.opts.animFunction, function () {} );
      me.toggle();
     });
    } else {
     //me.panel.toggle( me.opts.speed, me.opts.animFunction, function () {} );
     me.toggle();
    }
   }
   //me.button.toggleClass("deployed");
   return false;
  });

  // Control functions
  me.deployed = false;
  me.toggle = function( deployPanel ) {
   if( typeof open === 'boolean' ) me.deployed = deployPanel; // Forces open or close
   if( !me.deployed ) me.open();
   else               me.close();
  };

  var oldPadding = '0px';
  var oldWidth   = 0;
  me.open = function() {
   me.deployed = true;

   // Stores old values
   var w = me.button.width();
   if( w ) {
    oldPaddingR = me.button.css('padding-right');
    oldPaddingL = me.button.css('padding-left');
    oldWidth   = w;
   }
   me.button.addClass( 'deployed' );
   // Animates button
   if( me.left )
    me.button.animate( { 'width': '0px', 'padding-right': '0px' }, me.opts.speed, me.opts.animFunction );
   else
    me.button.animate( { 'width': '0px', 'padding-right': '0px', 'padding-left': oldPaddingR }, me.opts.speed, me.opts.animFunction );
   me.panel.toggle( me.opts.speed, me.opts.animFunction, function () {} );

   if ( me.opts.clickToClose ) me.enableCloseFn(); // Enables autoclose function if clickToClose is enabled
  };

  me.close = function() {
   me.deployed = false;

   me.panel.toggle( me.opts.speed, me.opts.animFunction, function () {} );
   // Animates button
   if( me.left )
    me.button.animate( { 'width': oldWidth + 'px', 'padding-right': oldPaddingR }, me.opts.speed, me.opts.animFunction );
   else
    me.button.animate( { 'width': oldWidth + 'px', 'padding-right' : oldPaddingR, 'padding-left': oldPaddingL }, me.opts.speed, me.opts.animFunction );
   me.button.removeClass( 'deployed' );

   if ( me.opts.clickToClose ) me.disableCloseFn(); // Disables autoclose function if clickToClose is enabled
  };

  // Autoclose panel when clicking outside
  me.stopCloseFn    = function(e) { e.stopPropagation(); }; // don't close panel when clicking inside it
        me.enableCloseFn  = function( ) { $(document).bind  ( 'click', me.close ); me.panel.bind  ('click', me.stopCloseFn ); };
        me.disableCloseFn = function( ) { $(document).unbind( 'click', me.close ); me.panel.unbind('click', me.stopCloseFn ); }; // Removes binds
 };
})(jQuery);

Código CSS
/*
 * jQuery slidePanel plugin basic styles
 * Version: 0.1.0 (2011/10/25)
 */

/* Widget styles */
.slidePanelWidget { position: fixed; .position: absolute; left: 0; }
.slidePanelWidget.right { left: auto; right: 0; }

  /* Button styles */
  .slidePanelWidget a.slidePanelButton:focus { outline: none; }

  .slidePanelWidget .slidePanelButton {
    white-space: nowrap;
    overflow: hidden;
    position: absolute;
    top: 4px;
    height: 19px;
    background-color: #BAC8DC;

    text-decoration: none;
    color: #fff;
    font-weight: bold;

    z-index: 2;
  }

  /* Left/right button styles */
  .slidePanelWidget .slidePanelButton {
   left: 0;
    padding: 1px 10px 1px 20px;
    background-position: 0 0;

           -moz-border-radius-topright: 10px;
        -khtml-border-top-right-radius: 10px;
       -webkit-border-top-right-radius: 10px;
               border-top-right-radius: 10px;

        -moz-border-radius-bottomright: 10px;
     -khtml-border-bottom-right-radius: 10px;
    -webkit-border-bottom-right-radius: 10px;
            border-bottom-right-radius: 10px;
  }
  .slidePanelWidget.right .slidePanelButton {
   left: auto; right: 0;
    padding: 1px 20px 1px 10px;
    background-position: 100% 0;

                   -moz-border-radius: 0;
                 -khtml-border-radius: 0;
                -webkit-border-radius: 0;
                        border-radius: 0;

           -moz-border-radius-topleft: 10px;
        -khtml-border-top-left-radius: 10px;
       -webkit-border-top-left-radius: 10px;
               border-top-left-radius: 10px;

        -moz-border-radius-bottomleft: 10px;
    -khtml-border-bottom-right-radius: 10px;
    -webkit-border-bottom-left-radius: 10px;
            border-bottom-left-radius: 10px;
  }

  /* Dynamic change of sprites. Sprites are 19px tall */
  .slidePanelWidget .slidePanelButton         { /* Open sprite */ background: #BAC8DC url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAoCAYAAAD+MdrbAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABZ0RVh0Q3JlYXRpb24gVGltZQAwMy8yMy8wOT6cudcAAAAfdEVYdFNvZnR3YXJlAE1hY3JvbWVkaWEgRmlyZXdvcmtzIDi1aNJ4AAAHUUlEQVRIiX2Xf2wT5xnHP3e++Ecck+AYnN8JhEDLKFFC0Bi0xQkZE5qQyqSxgSaxTevM+scKk6ZpmpAq/qq0sk3TOv6ZNmkl1VAlpk6bVDRSYIJ1RbRjpBAoSXGa1DjBjp3z2Xe27+7dH3c2SVbtlU6yz+997vnxfZ7ntSSEoLokSZIBL7AGiLhXI+B3txhAFsgAaSAPlIUQdo1RBUqS5AFCQDvQBwwCu4BeFyoAFbgP/Av4NzANJIG8EMKqAV3YWmATsA/41q5duzbv27dPjkajKIqCLMtUKhXm5uYYHx+3bt68eR8YAy654JwQwpIA2XVxC/C19vb2l44ePdrQ1dVFOp0ml8uh6zoAgUCApqYmIpEIiUSCsbGxpVQq9Trwtmt53gP4gC7gq11dXSePHTvWEAqFmJ6eRlVVTNOsxdg0TTRNI5PJEI1G2b59u39ycvKZYrE467quVl3dDpyMx+PtoVCImZkZqrGtQkqlErIsI8syQgiWlpaIRCL09PT4b9y4sR64BTyWgTAwODQ0tKWzs5O5uTmWL03TiMfjxONx8vk8Qojay+bm5ujr62NgYOALrlFhGVgH7BodHfWk02ksy1oB1HWdWCxGLBajWCxi23YNaFkWmUyGkZERBdgJNCvV7La3tzM1NYUQAtM00XWdcrlMLperwbPZLIqi4PP5qK+vp66ujlwuR3d3N8BmoElxk7JWURQymQwAqqpy4sQJTNNckZRTp06hKAqKonDmzBkaGxvRdZ0tW7bghi6g1BQuSRiGAUCxWGR4eJjV68CBA7XPp0+fxuv1IssykiTV7iuADqiGYTTX1dWhqipCCOLxOOVymWKxyPnz5wE4fPgwfr8fr9eLEAJd12lqaqoasgjoCpAD7s3Ozm6IRCLMz89j2zaTk5NYlrUihhMTEzQ2NiLLMoqiYBhGTeTAPSAr4xT59fHxcau3txfLsiiVSlQqFSzLWuFONbOWZVEul7Ftm97eXi5dulQB3gfSCk7n+M+tW7duT09PDwwODnLlyhWAmjyOHDmCW/OUy2VkWQZg9+7dPHjwgDt37twGJoCsBzBxOok2Ozu7Z+fOnb7W1lYSiQSlUgnbtkkmkySTSYQQ2LaNx+Nh7969eL1ezp0791jX9bPADWDeA9hABShqmpabnJzc1t/fH4jFYkiSRKFQQFVVLMsiEomwbds29u/fTzabZWxsLJtOp38BjAMzgLG8fTXh9L4R4Mjg4ODWWCymdHZ2rshqIpHg8uXLlbt3734IvAlcBz4BVCGE9XkNtsUF9+OU01M41STceE+6CfjIBc0D2ooGu0zc1RHQgKP8iGt5dQQUXZmlXd1pQEUsg9Qqxc2qDRiSJNk4JdkEeJbtU9yYa65VZVatz7MwAmzt+vrLLzfv2DfqDa8PyopPkmQZu1IS+vyn+cUP3r04++ff/sZ1P/P/hlR7eMfISx0HX/yhr6MvkMrpLGoGBaMCQNBfR7jBT0tTAGPmXnH27bO/yt2+fhZIVqHVLEtAW/PQ6E87Xzh+XGto8yQWVEy79uIVS5FletavIaglrYfnXn1NvXfzl8CCEELI7p4gcKDtwLHjWkObZyqVewKrGJBLQe4RlHUQAtO2mErlKDS0eToOfu9HwJeBegDZta5r/fMv/Mzf/bQnsaCuNCef5sLRfi4c7YelebAtR0BAYkEluHlH3bo9B08BHZKbhDpgqGX4cHcqZ6xyU0Ahx6HnBjn03A7QMmCbICxAYNo2qaUS658/tAkYADyya+pwfccmaVErOpCK7ri48BAys0/46VlY+ASySSgVQNgs5gsEO/tkYA9uxw4Cz8hKHYV80XlwMcWF73wRygaSubXGu/DjbyApXvAGOPS7axDuoFCp4Oleh2thsDpTokgymK5OS5rj4qp16CsjT778+m8QagZZqfbMVsBXqxTT0Ah6ZQpFA7z1xH7yOhh5hPqYq384A0Ds2ychGIZAA3gDUCkTDHkx9aIbcKeUysBC8dOPO8JrWigsqaD4uJoywKyDlFYz6urDHESbQTEdoGUSXhNCezgB8BlQlnHq8lrynTdojYZRsBxpyArU+cAfeuKmvwEUHyheABRJorUlTPKdPwrgH4CmAAXgL4sfvvti/t4HgZ4NG5ma/NjxQAgIrCF24ueOVPwh554rrZ6Nnah33yc38U8NuAjoHtd3HdhWSNztaxl4VgpF21EfZ7BNEySFmazBzJIJih8kZ+JtfGoz/sIC079/xbZ07SLwhhBCrdayD+fE+pYv0trW+91XpIZNgzyamWExlaKwtARCEFwTIhyN0tLZTv7BLRJjrwrj8Wf3gR8A7wkhSsu7TQin/b8GbGgeGvW0jH6TYNfTeHwBhG1hFfNoiQke/f1P5D56z8aZI9/HOSIXRPVo5kIlnBEwCvwV5zxtuyFZflnub28CX8Lp7lKVs7rBSq7Q1+Ic3IeBZ4EOF54ArgFXcOZJDudfQA2yArgK7MGp86D7EgGUXFXoQgjzfx4E/gsO25H1HQ5JOwAAAABJRU5ErkJggg==) no-repeat 0 0; }
    .slidePanelWidget .slidePanelButton:hover { background-color: #59B; background-position: 0 -19px; }
    .slidePanelWidget.right .slidePanelButton:hover { background-position: 100% -19px; }

  .slidePanelWidget .slidePanelButton.deployed         { /* Closed sprite */ background: #666 url(data:image/png;base64,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) no-repeat 0 0; }
    .slidePanelWidget .slidePanelButton.deployed:hover { background-color: #59B; background-position: 0 -19px; }
    .slidePanelWidget.right .slidePanelButton.deployed:hover { background-position: 0 -19px; }


  /* Panel styles */
  .slidePanelWidget .slidePanel {
    display: none;
    position: absolute;
    width: auto;
    height: auto;
    z-index: 1;

    padding: 8px;
    color:#CCC;
    background: #111;

      -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=90)";
          filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=90);
    -moz-opacity: .9;
         opacity: .9;
  }

  /* Left/right panel styles */
  .slidePanelWidget .slidePanel {
    left: 0;
           -moz-border-radius-topright: 4px;
        -khtml-border-top-right-radius: 4px;
       -webkit-border-top-right-radius: 4px;
               border-top-right-radius: 4px;

        -moz-border-radius-bottomright: 4px;
     -khtml-border-bottom-right-radius: 4px;
    -webkit-border-bottom-right-radius: 4px;
            border-bottom-right-radius: 4px;
  }
  .slidePanelWidget.right .slidePanel {
    left: auto;
    right: 0;

                   -moz-border-radius: 0;
                 -khtml-border-radius: 0;
                -webkit-border-radius: 0;
                        border-radius: 0;

           -moz-border-radius-topleft: 4px;
        -khtml-border-top-left-radius: 4px;
       -webkit-border-top-left-radius: 4px;
               border-top-left-radius: 4px;

        -moz-border-radius-bottomleft: 4px;
     -khtml-border-bottom-left-radius: 4px;
    -webkit-border-bottom-left-radius: 4px;
            border-bottom-left-radius: 4px;
  }


2011/10/21

Copiando a Google, sí, ¿y?. Flexbox y overlay/dialog.

Retomemos viejas costumbres. Llevaba algún tiempo sin publicar nada básicamente porque llegaron las vacaciones y por pereza, sinceridad ante todo. Pero hay que luchar contra la apatía, así que para abrir boca, os voy a poner un cachito de código copiado vilmente desde Chrome aplicando las últimas tecnologías y ese toque minimalista de Google, pero al alcance de todos los navegadores (modernos). Concretamente, os hablo del combo overlay/caja de diálogo que se usa en la configuración de Chrome pero fácilmente aplicable a cualquier web. A continuación, una captura.
Las tecnologías principales que se han usado son el juego de propiedades CSS3 denominado flexbox que permite definir cajas de elementos distribuyendolas tanto a ellas como a sus contenidos dentro de la página y  varias propiedades visuales de CSS3 junto con sus variantes según el navegador, puestas en el orden correcto. De regalo, un clon de los botones de Chrome también, que se ven muy bonitos.

Para más información sobre las propiedades de flexbox podéis ver un magnífico artículo en HTMLRocks. Es tontería explicarlo de nuevo aquí porque es una página muy trabajada con muchos ejemplos y que hasta detecta si el navegador usado para verla es capaz de mostrar correctamente todos los ejemplos, así que no os pongáis a verla con el IE6, que os veo y luego me venís llorando!.

Para ver el código completo, en mi jsFiddle como es habitual.


El ejemplo se compone de dos partes. Por un lado una estructura muy sencilla en HTML
<!-- Overlay/dialog -->
<div id="overlay" class="overlay">
    <div id="myDialog" class="dialog">

        <!-- Título del diálogo -->
        <h1>Título del Diálogo</h1>

        <!-- Cuerpo del diálogo -->
        <div class="dialog-area">
            <p>CUERPO</p>
        </div

        <!-- Pie del diálogo -->
        <div class="dialog-footer">
            <div class="extensible">PIE con botones a la derecha sin float</div>
            <input type="button" value="Botón1"/>
            <input type="button" value="Botón2"/>
        </div>

    </div>
</div>

Y por el otro, un CSS algo más complejo (CSS3, por supuesto). Los comentarios además de ayudar a identificar los estilos, dan información sobre para qué y cómo se usan.

/* ================================================================================ */
/* Sample styles */
html {
    font-family: arial;
}

/* Sample dialog styles */
#myDialog {
    background-color: white;
    margin-bottom: 6px;
    margin-top: 6px;
    width: 500px;
}


/* ================================================================================ */
/* Overlay/Dialog styles */
.overlay {
    -webkit-transition: 0.25s opacity;
       -moz-transition: 0.25s opacity;
        -ms-transition: 0.25s opacity;
         -o-transition: 0.25s opacity;
            transition: 0.25s opacity;  

    background: -webkit-radial-gradient(rgba(127, 127, 127, 0.5), rgba(127, 127, 127, 0.5) 35%, rgba(0, 0, 0, 0.7));
    background:    -moz-radial-gradient(rgba(127, 127, 127, 0.5), rgba(127, 127, 127, 0.5) 35%, rgba(0, 0, 0, 0.7));
    background:      -o-radial-gradient(rgba(127, 127, 127, 0.5), rgba(127, 127, 127, 0.5) 35%, rgba(0, 0, 0, 0.7));
    background:     -ms-radial-gradient(rgba(127, 127, 127, 0.5), rgba(127, 127, 127, 0.5) 35%, rgba(0, 0, 0, 0.7));
    background:         radial-gradient(rgba(127, 127, 127, 0.5), rgba(127, 127, 127, 0.5) 35%, rgba(0, 0, 0, 0.7));
    
    position: fixed;
    left: 0; top: 0; right: 0; bottom: 0;
    padding: 20px; padding-bottom: 130px;
    z-index: 10;
}


.overlay .dialog {
    -webkit-user-select: none;
       -moz-user-select: none;

    -webkit-box-shadow: 0px 5px 80px #505050;
       -moz-box-shadow: 0px 5px 80px #505050;
         -o-box-shadow: 0px 5px 80px #505050;
        -ms-box-shadow: 0px 5px 80px #505050;
            box-shadow: 0px 5px 80px #505050;

    background: white;
    border: 1px solid #BCC1D0;

    -webkit-border-radius: 2px;
       -moz-border-radius: 2px;
     -khtml-border-radius: 2px;
            border-radius: 2px;

    position: relative;
    min-width: 400px;
    padding: 0;
}


/* Dialog heading */
.overlay .dialog h1 {
    -webkit-padding-end: 24px; /* Specifies the space between an element's left or right border and its contents, depending on the writing direction */
       -moz-padding-end: 24px;

    -webkit-user-select: none;
       -moz-user-select: none;

    background: -webkit-linear-gradient(white, #F8F8F8);
    background:    -moz-linear-gradient(white, #F8F8F8);
    background:      -o-linear-gradient(white, #F8F8F8);
    background:         linear-gradient(white, #F8F8F8);

    border-bottom: 1px solid rgba(188, 193, 208, .5);
    padding: 10px 15px 8px 15px;
    margin: 0;

    font-size: 105%;
    font-weight: bold;
    color: #53637D;
    text-shadow: white 0 1px 2px;
}


/* Dialog area */
.overlay .dialog .dialog-area {
    -webkit-user-select: none;
       -moz-user-select: none;

    padding: 10px 15px;
}


/* Dialog footer */
.overlay .dialog .dialog-footer {

    border-top: 1px solid rgba(188, 193, 208, .5);
    padding: 12px;
}


/* ================================================================================ */
/* Flexible box model */
/* http://www.html5rocks.com/en/tutorials/flexbox/quick/ */
.overlay {
    display: -webkit-box;            /* Enables flexible box model */
    -webkit-box-orient: horizontal; /* How should the box's children be aligned */
      -webkit-box-pack: center;    /* Sets the alignment of the box along the box-orient axis. . So if box-orient is horizontal it will chose how the box's children are aligned horizontally, and vice-versa. */
     -webkit-box-align: center;   /* Basically box-pack's brother property. Sets how the box's children are aligned in the box. If the orientation is horizontal it will decide the alignment vertically and vice-versa */

    display: -moz-box;
    -moz-box-orient: horizontal;
      -moz-box-pack: center;
     -moz-box-align: center;

    display: box;
    box-orient: horizontal;
      box-pack: center;
     box-align: center;
}

.overlay .dialog .dialog-footer {
    display: -webkit-box;
    -webkit-box-orient: horizontal;
      -webkit-box-pack: end;
     -webkit-box-align: center;

    display: -moz-box;
    -moz-box-orient: horizontal;
      -moz-box-pack: end;
     -moz-box-align: center;

    display: box;
    box-orient: horizontal;
      box-pack: end;
     box-align: center;
}

.extensible {
    -webkit-box-flex: 1;  /* Float value. Extensible up to 1 (or value) times its width (or height depending on parent's orientation) */
       -moz-box-flex: 1;
            box-flex: 1;
}


/* ================================================================================ */
/* Chrome buttons */
button, input[type="button"], input[type="submit"] {
    -webkit-border-radius: 2px;
       -moz-border-radius: 2px;
     -khtml-border-radius: 2px;
            border-radius: 2px;

    -webkit-box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.1);

    -webkit-user-select: none;
       -moz-user-select: none;

    background: -webkit-linear-gradient(#FAFAFA, #F4F4F4 40%, #E5E5E5);
    background:    -moz-linear-gradient(#FAFAFA, #F4F4F4 40%, #E5E5E5);
    background:      -o-linear-gradient(#FAFAFA, #F4F4F4 40%, #E5E5E5);
    background:         linear-gradient(#FAFAFA, #F4F4F4 40%, #E5E5E5);

    border: 1px solid #AAA;
    color: #444;
    font-size: inherit;
    margin-bottom: 0px;
    min-width: 4em;
    padding: 3px 12px 3px 12px;
}

2011/07/15

Plugin jQuery Sliding Buttons

Después de algún tiempo de diseño cociendo algunas cosas, he salido de la cueva y os presento otro plugin de jQuery totalmente hecho por mí. Ante vosotros está el magnífico Sliding Buttons!

          

Este plugin transforma un inocente span o div con dos contenedores de tipo inline dentro en un vistoso botón cuyos contenidos (primer contendor) aparece y desaparece al pasar el ratón sobre él. Soporta eventos y funciona!

Ahí va el jsFiddle de rigor y como siempre, si encuentras algún bug o lo mejoras, decídmelo, y si lo usas, cuéntamelo también, que al menos no tenga sensación de haber perdido el tiempo.


Ejemplo

Código javascript
/**
 * jquery.slideButton-0.1.js - Slide Button plugin for jQuery
 * ==========================================================
 *  (C) 2011 José Ramón Díaz - jrdiazweb@gmail.com
 *
 * http://3nibbles.blogspot.com/2011/07/plugin-jquery-sliding-buttons.html
 *
 * INSTANTIATION
 * Any container with the .slideButton class will be initialized
 * on domReady event using the options defined in object
 * "slideButtonDefaults". This feature can be disabled by setting
 * the variable "slideButtonAutoload" to the false value.
 *
 * Manual instantiation
 *     $('.slideButton').slideButton( [ options_object ] );
 *
 * OPTIONS
 *     - direction    : 'left',   Slide direction "left" = LtR, "right" = RtL
 *     - border       : '3px',    Border width == difference in height of control(>) and contents(<)
 *     - radius       : '20px',   Border radius of the slideButton
 *     - height       : '36px',   Maximum height of the slideButton. Includes all borders
 *     - width        : '70px',   Minimum width. Applied to control
 *     - deployedWidth: '225px',  Maximum width. Applied to container and deployed contents
 *     - inAnimation  : 'swing',  Function used to deploy (slide in) the elements. For more functions needs the easing packs
 *     - outAnimation : 'swing',  Function used to hide (slide out) the elements. For more functions needs the easing packs
 *     - inSpeed      : 500,      deploy effect duration
 *     - outSpeed     : 300,      hide effect duration
 *     - onClick      : null,     button click trigger callback function
 *     - onBlur       : null,     blur (slide out) trigger callback function
 *     - onDeploy     : null,     deploy (slide in) trigger callback function
 *     - onHide       : null,     hide (end of slide out) trigger callback function
 *
 * API
 *     - show()    Deploys the sliding contents
 *     - hide()    Hides the sliding contents
 *     - lock()    Locks the sliding contents open
 *     - locked    Indicates that the control is locked
 *     - deployed  Indicates that the control is deployed
 *
 * CLASSES
 *     - Container: .slideButton, .deployed, .clicked
 *     - Button:    .button
 *     - Content:   .content
 *
 * HTML format
 *     *
 * Valid HTML content and control containers are any inline element (a, span, etc...).
 * Container must be div or span.
 *
 * 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.
 */
var slideButtonDefaults = {};   // The default options used for all auto loading sliding buttons
var slideButtonAutoload = true; // Enables the autoinstantiation feature for .slidingButton

(function( $ ) {
    // Private members
    function SlideButton( el, options ) {
        this.container = $(el);
        this.cA = null;
        this.cB = null;

        //this.el.attr('locked', 'off');

        // States control
        this.locked   = false;
        this.deployed = false;

        // Default options
        this.options = {
                direction    : 'right',   // Slide direction "left" = LtR, "right" = RtL
                border       : '3px',    // Border width == difference in height of control(>) and contents(<)
                radius       : '20px',   // Border radius of the slideButton
                height       : '36px',   // Maximum height of the slideButton. Includes all borders
                width        : '70px',   // Minimum width. Applied to control
                deployedWidth: '225px',  // Maximum width. Applied to container and deployed contents

                inAnimation  : 'swing',  // Function used to deploy (slide in) the elements. For more functions needs the easing packs
                outAnimation : 'swing',  // Function used to hide (slide out) the elements. For more functions needs the easing packs
                inSpeed      : 300,      // deploy effect duration
                outSpeed     : 200,      // hide effect duration

                onClick      : null,     // button click trigger callback function
                onBlur       : null,     // blur (slide out) trigger callback function
                onDeploy     : null,     // deploy (slide in) trigger callback function
                onHide       : null,     // hide (end of slide out) trigger callback function
        };

        slideButtonDefaults = this.options; // Sets the initial value of global default options

        this.setOptions( options );
        this.initialize();
    }

    // Instantiation
    $.fn.slideButton = function( options ) {
        return new SlideButton(this.get(0) || $('<span />') || $('<div />'), options);
    };

    // Public Members
    SlideButton.prototype = {
        // Plugin functions
        killerFn: null,

        initialize: function() {
            var me, o, c, children, cA, cB, h1, h2, w1, w2, w3, r1, r2, b;
            me = this;
            o  = me.options;
            c  = me.container;

            // Sets objects and variables initial values
            children = c.children();
            cA = me.cA = $( children[0] );
            cB = me.cB = $( children[1] );

            // Widths and heights
            b  =   parseInt(o.border);                  // Border
            h1 =   parseInt( o.height ) + 'px';         // Container/Contents height
            h2 = ( parseInt( h1 ) - (b * 2) ) + 'px'; // Control height
            w1 =   parseInt( o.deployedWidth ) + 'px';  // Container and deployed width
            w2 =   parseInt( o.width ) + 'px';          // Contents width
            w3 = ( parseInt( w2 ) - (b * 2) ) + 'px';  // Control width
            r1 =   parseInt( o.radius ) + 'px';         // External border radius
            r2 = ( parseInt( o.radius ) - b ) + 'px'; // Internal border radius

            // Container modifications
            if( !c.hasClass('slideButton') ) me.container.addClass( 'slideButton' );
            c.width( w1 );
            c.height( h1 );
            //me.container.height(o.height);
            c.css({ 'position': 'relative', 'overflow': 'hidden' });

            // Chidren modifications
            cA.addClass( 'content' ); //o.direction + 'A content' );
            cB.addClass( 'button'  ); //o.direction + 'B button' );

            // Wraps sliding contents into a span
            var text = cA.get(0).innerHTML;
            cA.get(0).innerHTML = '' + text + '';
            cA.find('span:first-child').hide();

            // Sets styles
            c.data( 'initialWidth', w1 ); // Stores the initial width in container data
            cA.width( w2 );
            cB.width( w3 );
            cA.height( h1 );
            cB.height( h2 );
            cA.css({ 'position': 'absolute', 'top': '0px' , 'lineHeight': h1, 'zIndex': 0, 'borderRadius': r1 });
            cB.css({ 'position': 'absolute', 'top': b+'px', 'lineHeight': h2, 'zIndex': 1, 'borderRadius': r2 });

            if(o.direction == 'left')
            {
                cA.css({ 'right': '0px' , 'textAlign': 'left' });
                cB.css({ 'right': b+'px' });
            }
            else
            {
                cA.css({ 'left': '0px' , 'textAlign': 'right' });
                cB.css({ 'left': b+'px' });
            }

            // Killer function set
            this.killerFn = function( e ) {
                // Test if didn't clicked on the slideButton
                var tar = $(e.target);
                if (tar.parents( '.slideButton' ).size() === 0) {
                    me.locked   = false;
                    me.cB.removeClass( 'clicked' );
                    me.hide();
                    me.disableKillerFn();
                }
            };

            // Stores slideButton instance in container
            c.data( 'slideButton', me );

            // Event listeners
            cB.hover( me.show, me.hide );
            cB.click( me.click );
        },

        setOptions: function(options) {
            var o = this.options;
            var c = this.container;

            // Uses container height if no options.height is given
            if(typeof(o.height) === 'undefined' || o.height == null || o.height <= 0)
                if(c.height()) o.height = c.height();

            $.extend(o, options);
        },

        // Slide visualization functions
        show: function() {
            var me;
            if( typeof this.container !== 'undefined' )
                me = this.cB;
            else
                me = $(this);

            // Gets the instance
            var sb = me.parent().data( 'slideButton' );
            if(sb.deployed) return; // It is already deployed
            var o  = sb.options;

            var slidelem = me.prev();
            // Animates the contents of control
            slidelem.stop().animate( { 'width': parseInt(o['deployedWidth']) + 'px' }, o['inSpeed'] ); //, o['inAnimation'] );
            // Animates the contents of content
            slidelem.find( 'span' ).stop( true, true ).fadeIn();

            // Updates the state
            sb.container.addClass('deployed');
            sb.deployed = true;

            // Triggers events
            if( typeof o.onDeploy === 'function' ) o.onDeploy(sb);
        },

        hide: function(force) {
            var me;
            if(typeof this.container !== 'undefined')
                me = this.cB;
            else
                me = $(this);

            if( typeof force !== 'object' && force == true ) me.removeClass('clicked');
            if( me.hasClass('clicked') ) return; // Ignore hide when in clicked (locked) state

            // Gets the instance
            var sb = me.parent().data( 'slideButton' );
            if(!sb.deployed) return; // Already hidden
            var o  = sb.options;

            var slidelem = me.prev();
            // Animates the contents of control
            slidelem.stop().animate( { 'width': parseInt(o.width)+'px' }, o['outSpeed'], o['outAnimation'] );
            // Animates the contents of content
            slidelem.find( 'span' ).stop( true, true ).fadeOut();

            // Updates the state
            sb.container.removeClass( 'deployed' );
            sb.deployed = false;
            sb.locked   = false;

            // Triggers events
            if( typeof o.onHide === 'function' ) o.onHide(sb);
        },

        click: function (ev) {
            var me = $(this);

            // Gets the instance
            var sb = me.parent().data( 'slideButton' );
            var o  = sb.options;
            if( me.hasClass( 'clicked' ) )
            {
                me.removeClass( 'clicked' );
                sb.hide();
                sb.disableKillerFn();
            }
            else
            {
                sb.show();
                sb.enableKillerFn();
                me.addClass( 'clicked' );
            }

            // Triggers events
            if( typeof o.onClick === 'function' ) o.onClick(sb);
        },

        lock: function() {
            var sb = $(this).data( 'slideButton' );
            if(!sb.deployed) sb.show();
            sb.enableKillerFn(); // Hooks the click event on the document to close
            sb.locked = true;
        },

        // Lock killer functions
        enableKillerFn: function() {
            var me = this;
            $(document).bind( 'click', me.killerFn );
        },

        disableKillerFn: function() {
            var me = this;
            $(document).unbind( 'click', me.killerFn );
        },

    };

}(jQuery));


// Autoinitializarion
$(document).ready(function() {
    if(slideButtonAutoload)
        $( '.slideButton' ).each( function ( index, elem ) {
            $(elem).slideButton( slideButtonDefaults );
        });
});


///////////////////////////////////////////////////////////////////////
/*
 * Border-radius jQuery cssHook
 * ==========================================================
 * (C) 2011 José Ramón Díaz - jrdiazweb@gmail.com
 *
 * Instead of using borderRadius cssHook from Brandon Aaron for example
 * (https://github.com/brandonaaron/jquery-cssHooks), I define a custom
 * cssHook for the borderRadius CSS property for the sake of simplicity
 * and to reduce other modules dependencies.
 *
 * If you prefer to use another cssHook from another source (Brandon's
 * one for example), just delete this and include the other cssHook.
 *
 * NOTE: cssHooks needs jQuery v1.4.3 or greater.
 */
(function( $ ){

    var div      = document.createElement('div'),
        divStyle = div.style;

    if ( !$.cssHooks ) {
        // if not, output an error message
        throw("jQuery 1.4.3 or above is required for this plugin to work");
        return;
    }

    div = null; // Avoids IE memory leaks

    $.support.borderRadius =
        divStyle.MozBorderRadius    === ''? 'MozBorderRadius'    :
       (divStyle.msBorderRadius     === ''? 'msBorderRadius'     :
       (divStyle.WebkitBorderRadius === ''? 'WebkitBorderRadius' :
       (divStyle.OBorderRadius      === ''? 'OBorderRadius'      :
       (divStyle.borderRadius       === ''? 'borderRadius'       :
        false))));

    // Border radius will be set only in border-radius compatible "borderRadius" browsers
    if ( $.support.borderRadius && $.support.slideBorderRadius !== "borderRadius" )
    {

        $.cssHooks["borderRadius"]    = {

                get: function( elem, computed, extra ) {
                    return $.css( elem, $.support.borderRadius );
                },
                set: function( elem, value) {
                    elem.style[$.support.borderRadius] = value;
                }

        };

    }

})(jQuery);
CSS
/**
 * Slide Button Styles.
 * ================================================
 * (C) 2011 José Ramón Díaz - jrdiazweb@gmail.com
 *
 * http://3nibbles.blogspot.com/2011/07/plugin-jquery-sliding-buttons.html
 *
 * Slide Button CSS styles. Note that rounded borders is a CSS3 feature,
 * so, it will be represented correctly in CSS3 ready browsers.
 *
 * Legal stuff
 *     You are free to use this CSS, but you must give credit 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.
 */
.slideButton                                   { font-weight: bold; font-size: 11px; font-family: Arial } /* Container                 */
  .slideButton a                               { text-decoration: none; }                                 /* a components containers   */

  .slideButton .button                         { background-color: #FFFFFF; color: #000000; text-align: center;
                                                 cursor: pointer; text-transform: uppercase; }            /* Control                   */
    .slideButton.deployed .button              { background-color: #9AFF66; color: #FFFFFF;  }            /* Control - Deployed state  */

  .slideButton .content                        { background-color: #36A300; color: #FFFFFF; }             /* Content                   */
    .slideButton.deployed .content             {  }                                                       /* Content - Deployed state  */

/* Depends on contents of the content component */
  .slideButton .button span                    { color: #9AFF66; }                                        /* Control spans             */
    .slideButton.deployed .button span         { color: #36A300; }                                        /* Control spans - Deployed  */
  .slideButton .content span span              { color: #9AFF66; }                                        /* Content spans             */
    .slideButton.deployed .content span        {  }                                                       /* Content spans - Deployed  */

2011/07/06

Sprite Cow

Una de las mejores herramientas que he visto para la gestión de sprites es SpriteMe

Pero tener una buena herramienta de generación no sirve de nada si no queda completamente definido qué sprites son los que están en la imagen para incluirlos en el CSS. Aquí entra en juego SpriteCow, que de forma visual y muy sencilla permite seleccionar áreas de la imagen previamente cargada en un canvas, generándonos el CSS para acceder a los sprites.



Con ello, los pasos serían
  1. Generar la imagen del sprite. Ya sea a lo campestre con el photoshop o similar o con SpriteMe
  2. Cargar la imagen con SpriteCow y seleccionar los sprites que se deseen, copiando y pegando el CSS generado.
Rápido y sencillo, como debe ser.