2011-03-19 19 views
9

Mi primera pregunta aquí. :)Reducción/crecimiento de la transición de círculo de JavaScript

Estoy buscando una transición entre dos imágenes donde la imagen primero se contrae en forma de círculo y luego el círculo vuelve a crecer con la otra imagen. Es difícil de explicar, y puedo estar usando las palabras equivocadas, porque no puedo encontrar nada al respecto en el Interwebz.

Estoy hablando de un efecto como el final de Loony Toons. http://www.youtube.com/watch?v=ZuYIq-J5l9I

Ese encogimiento de negro, ¿se puede hacer en JavaScript/JQuery?

Respuesta

7

TL: DR

¿Cómo puedo siquiera empezar a describir este? Sería mucho más fácil si el estándar CSS 2 clip admite algo más que un valor "rect", es decir, un "círculo" o "elipse", pero ... ya que no existe, he hecho todo lo posible para pieza algo juntos harán lo que estás pidiendo. Las advertencias son muchas. Una de ellas es que esto solo funcionará en algo con un fondo de color sólido en caso de que quisiera que la imagen se recortara en segundo plano. Otra es que, aunque traté de dar cuenta del tiempo de actualización de CSS en todos los navegadores, el procesamiento aún no es "perfecto". Mi enfoque inicial fue simplemente animar el clip de la imagen que se estaba reemplazando, pero no funcionó debido a la forma en que se realizaron las actualizaciones al recorte a través de la función de suavizado en el complemento que localicé. El acercamiento final está abajo.

El enfoque

El concepto es la creación de la imagen como un background-image propiedad de un recipiente como un <div> con un background-position de center center y el position del recipiente para relative, o algo no estático. El siguiente es generar los elementos de recorte como elementos secundarios del contenedor. La primera es una imagen de círculo de recorte position: absolute del color de su fondo, ya sea PNG transparente o GIF (prefiero la primera), y las siguientes cuatro son divs, también con absolute posiciones que tienen atributos left, right, top y bottom establecidos a 0 para cada uno de los lados respectivos que cortarán. La idea es animar el top, left, width y height de la imagen del círculo de recorte y sincronizar el ancho y alto de los divisores de división usando la opción de devolución de pasos de la llamada .animate() haciendo coincidirlos con el left actual y top valores. Entre las animaciones, cambie el background-image del contenedor a la nueva imagen y luego inicie la animación en la dirección opuesta.

Esto requirió un poco de fineessing en IE7, 8 y navegadores Webkit ya que la animación se recortó mucho más limpiamente en Firefox e IE9. Esta sería la variable adjust que verá en la demostración operativa.

El código de ejemplo es el siguiente:

El marcado de

<div class="imageContainer image1"> 
    <img class="clip" src="http://www.mysite.com/images/clipCircle.png" /> 
    <div class="top fill"></div> 
    <div class="left fill"></div> 
    <div class="right fill"></div> 
    <div class="bottom fill"></div> 
</div> 

El CSS

div.imageContainer 
{ 
    background-position: center; 
    width: 300px; 
    height: 300px; 
    position: relative; 
} 

img.clip 
{ 
    width: 100%; 
    height: 100%; 
    position: absolute; 
} 

div.fill 
{ 
    position: absolute; 
    background-color: White; 
} 

div.left, div.right 
{ 
    height: 100%; 
    top: 0; 
    width: 0; 
} 

div.left 
{ 
    left: 0; 
} 

div.right 
{ 
    right: 0; 
} 

div.top, div.bottom 
{ 
    width: 100%; 
    left: 0; 
    height: 0; 
} 

div.top 
{ 
    top: 0; 
} 

div.bottom 
{ 
    bottom: 0; 
} 

The Script

var speed = 1000; 

$clip = $("img.clip"); 

$clip.animate({ 
    top: $clip.parent().height()/2, 
    left: $clip.parent().width()/2, 
    width: 0, 
    height: 0 
}, { 
    duration: speed, 
    step: function(now, fx) { 
     switch (fx.prop) { 
     case "top": 
      $("div.top").css("height", now); 
      $("div.bottom").css("height", now + adjust);  
      break; 
     case "left": 
      $("div.left").css("width", now); 
      $("div.right").css("width", now + adjust); 
     } 
    }, 
    complete: function() { 
     $(this).parent().addClass("image2"); 

     $(this).animate({ 
      top: 0, 
      left: 0, 
      width: $clip.parent().width(), 
      height: $clip.parent().height() 
     }, { 
      duration: speed, 
      step: function(now, fx) { 
       switch (fx.prop) { 
       case "top": 
        $("div.top").css("height", now); 
        $("div.bottom").css("height", now + adjust);  
        break; 
       case "left": 
        $("div.left").css("width", now); 
        $("div.right").css("width", now + adjust); 
       } 
      }, 
      complete: function() { 
       $("div.imageContainer > *").removeAttr("style"); 
      } 
     }); 
    } 
}); 

EDIT:

La solución CSS3

Cuando la compatibilidad entre navegadores es una preocupación menor, CSS3 es una opción (aunque probablemente me propongo ver qué se puede hacer con el nuevo Canvas HTML5 para este tipo de animación). Hay un par de cosas a tener en cuenta:

  • La imagen debe estar dentro de un contenedor para poder acortar hacia su centro en lugar de su esquina superior izquierda.
  • El atributo border-radius no recortará las imágenes secundarias dentro de un contenedor. Por este motivo, la imagen debe convertirse en el atributo imagen de fondo del contenedor.
  • jQuery actualmente no anima correctamente border-radius. Puede reemplazar la funcionalidad animada jQuery actual para ese atributo o crear un objeto de animación borde-radio personalizado para hacer que jQuery se comporte mejor. He optado por este último. El radio del borde de cada esquina debe estar animado por separado.
  • La animación de entrada o salida consta de dos segmentos separados y, como resultado, la función de relajación "lineal" probablemente se utilice mejor para obtener resultados más limpios.

El método se comentó en línea a continuación:

El marcado de

<div class="imageContainer image1"> 
</div> 

El CSS

div.imageContainer 
{ 
    background-position: 0px 0px; 
    background-repeat: no-repeat; 
    width: 300px; 
    height: 300px; 
    position: absolute; 
    top: 0; 
    left: 0; 
} 

div.image1 
{ 
    background-image: url(http://www.mysite.com/images/myFirstImage.png); 
} 

div.image2 
{ 
    background-image: url(http://www.mysite.com/images/mySecondImage.png); 
} 

The Script

// Total animation speed in or out will be speed * 1.5 
var speed = 600; 

// Store a reference to the object to be clipped 
var $clip = $("div") 

// A function to build a mapping object for border radius parameters 
var buildRadiusObj = function(value) { 

    // Dimension an option object 
    var opts = {}; 

    // Use specialized Mozilla CSS attributes when needed 
    var attributes = $.browser.mozilla ? 
     ["-moz-border-radius-topleft", 
     "-moz-border-radius-bottomleft", 
     "-moz-border-radius-topright", 
     "-moz-border-radius-bottomright"] : 
     ["border-top-left-radius", 
     "border-bottom-left-radius", 
     "border-top-right-radius", 
     "border-bottom-right-radius"]; 

    // Build the option object 
    $.each(attributes, function(i, key) { 
     opts[key] = value; 
    }); 

    // Return the result 
    return opts; 
} 

$clip.animate(buildRadiusObj($clip.width() * 0.5), { // Animate the border radius until circular 
    duration: speed * 0.5, 
    easing: "linear" 
}).animate({           // Resize and reposition the container 
    width: 0, 
    left: $clip.width()/2, 
    height: 0, 
    top: $clip.height()/2 
}, { 
    duration: speed, 
    easing: "linear", 
    step: function(now, fx) {       // Synch up the background-position 
     if (fx.prop == "top") { 
      $(this).css("background-position", "-" + $(this).css("top") + " -" + $(this).css("left")); 
     } 
    }, 
    complete: function() {        // Swap the image 
     $(this).addClass("image2"); 
    } 
}).animate({           // Restore position and size 
    width: $clip.width(), 
    left: 0, 
    height: $clip.height(), 
    top: 0 
}, { 
    duration: speed, 
    easing: "linear", 
    step: function(now, fx) {       // Synch the background-position 
     if (fx.prop == "top") { 
      $(this).css("background-position", "-" + $(this).css("top") + " -" + $(this).css("left")); 
     } 
    }, 
    complete: function() {        // Remove inline styles but reapply border-radius 
     $(this).removeAttr("style").css(buildRadiusObj($clip.width() * 0.5)); 
    } 
}).animate(buildRadiusObj(0), {       // Restore border-radius to block 
    duration: speed * 0.5, 
    easing: "linear", 
    complete: function() { 
     $(this).removeAttr("style");     // Remove inline styles 
    } 
}); 

Again, the demo is located here.

+0

Eso se ve muy bien, aunque probablemente también lo necesite para trabajar en segundo plano. Debería haberlo mencionado, pero no sabía en el momento en que publiqué la pregunta. : -/ – GolezTrol

+0

Pero pensé que podría poner la imagen en un div con bordes redondeados (css3) y reducir ese div. La imagen de fondo se truncará en el borde incluso si está redondeada, ¿verdad? Entonces solo necesito animar el tamaño y la posición de ese div y la posición del fondo. En IE y (otros) navegadores más antiguos, se animará como un cuadrado, pero puedo vivir con eso si me da la posibilidad de usar esta animación sobre un fondo. Trataré de descubrir cómo funciona esta animación y cómo puedo ajustarla a mis necesidades. Gracias por la gran cantidad de esfuerzo. – GolezTrol

+0

@GolezTrol Bueno, esto probablemente sea mucho más fácil si no te importan las soluciones de navegador cruzado, por lo que vale. Sin embargo, en el mundo de IE7/8 ... oy ... Sí, esto será un desastre sin Flash o similar. – lsuarez

1

Vine esto, espero que sea interesante: http://www.netzgesta.de/transm/. La transición circles_out con un círculo podría hacer el trabajo, creo.

+0

Eso se ve bien, aunque todavía no he podido hacerlo funcionar. Intentaré algo más esta tarde. – GolezTrol

0

Aquí tienes. http://jquery.malsup.com/cycle/ Mira el zoom. Algo se puede resolver con la parte del círculo.

+0

Sí, encontré ese, pero no es lo que necesito. No quiero contraer la imagen en sí, sino solo el espacio que la rodea, de modo que se hace visible una parte cada vez más pequeña de la imagen. – GolezTrol

0

He intentado un poco más y se le ocurrió la idea de utilizar un elemento <canvas>.

Consulte el resultado en: http://jsfiddle.net/3MG8e/2/.

var cv = $('canvas')[0]; 
var ctx = cv.getContext('2d'); 
ctx.fillStyle = 'black'; 

var int = null; 
var t = -1; 
var amount = 50; 
var time = 1000; 
var size = 0; 

var im = new Image(); 
im.src = "http://burzak.com/proj/fxcanvas/docs/images/mario2.png"; 
im.onload = function() { 
    size = im.width; 
    int = setInterval(update, time/amount); 
} 

function update() { 
    if(++t >= amount) { 
     clearInterval(int); 
    } 
    ctx.fillRect(0, 0, cv.width, cv.height); 
    ctx.beginPath(); 
    ctx.arc(size/2, size/2, 
      size/2 - t * (size/2)/amount, 
      0, Math.PI*2, 
      false); 
    ctx.clip(); 
    ctx.drawImage(im, 0, 0, size, size); 
} 
Cuestiones relacionadas