JavaScript shrinking/growing circle transition
My first question here. :)
I'm looking for a transitions between two images where the image first shrinks in a circle shape and then the circle grows again containing the other image. It's hard to explain, and I may be using the wrong words, because I can't find anything about it on the Interwebz.
I'm talking about an effect like the Loony Toons ending. http://www.youtube.com/watch?v=ZuYIq-J5l9I
That shrinking-t开发者_JAVA技巧o-black, can it be done in JavaScript/JQuery?
TL:DR
How do I even begin to describe this one? It would be a lot easier if the CSS 2 clip standard supported anything besides a "rect" value, namely a "circle" or "ellipse" but... since that doesn't exist, I've done my best to piece something together that will do what you're asking. The caveats are many. One is that this is only going to work on something with a solid color background in the event you wanted the picture to clip to the background. Another is that while I've tried to account for the CSS update timing across browsers, the rendering still isn't "perfect." My initial approach was to simply animate the clip on the image that was getting replaced, but that didn't work due to the way updates were made to the clipping via the easing function in the plugin I located. The final approach is below.
The Approach
The concept is to set the image as a background-image
property of a container like a <div>
with a background-position
of center center
, and the position
of the container to relative
, or anything non-static. The next is to generate the clipping elements as children of the container. The first is a position: absolute
clipping circle image of the color of your background, either transparent PNG or GIF (I prefer the former), and the next four are divs, also with absolute
positions that have left
, right
, top
, and bottom
attributes set to 0 for each of the respective sides they will clip. The idea is to animate the top
, left
, width
, and height
of the clipping circle image and synch up the width and height of the clipping divs using the step callback option of the .animate() call by matching them to the current left
and top
values. Between animations, you change the background-image
of the container to the new image and then start the animation back in the opposite direction.
This required a little finessing in IE7, 8, and Webkit browsers as the animation clipped much more cleanly in Firefox and IE9. This would be the adjust
variable you'll see in the working demo.
The sample code is below:
The Markup
<div class="imageContainer image1">
<img class="clip" src="clipCircle.png" />
<div class="top fill"></div>
<div class="left fill"></div>
<div class="right fill"></div>
<div class="bottom fill"></div>
</div>
The 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:
The CSS3 Solution
When cross-browser compatibility is less of a concern, CSS3 is an option (although I'd probably suggest seeing what can be done with the new HTML5 Canvas for this kind of animation). There are a couple things to note:
- The image must be inside a container in order to allow us to clip toward its center rather than its top left corner.
- The border-radius attribute will not clip the child images inside a container. For this reason, the image must become the background-image attribute of the container.
- jQuery does not currently animate border-radius correctly. You can either replace the current jQuery animate functionality for that attribute or build a custom border-radius animation object to make jQuery more well-behaved. I have opted for the latter. Each corner's border-radius must be animated separately.
- The animation in or out consists of two separate segments, and as a result the "linear" easing function is probably best used for cleanest results.
The method is commented inline below:
The Markup
<div class="imageContainer image1">
</div>
The 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(/images/myFirstImage.png);
}
div.image2
{
background-image: url(/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.
I came this across, I hope it is interesting: http://www.netzgesta.de/transm/. The transition circles_out
with one circle could do the job I think.
Here you go. http://jquery.malsup.com/cycle/ Check out the zoom. Something can be worked out with the circle part.
I tried some more and came up with the idea of using a <canvas>
element.
Please see the result at: 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);
}
精彩评论