annotate src/DML/VendorAssetsBundle/Resources/assets/jquery.quicksand/1.4/jquery.quicksand.js @ 1:f38015048f48 tip

Added GPL
author Daniel Wolff
date Sat, 13 Feb 2016 20:43:38 +0100
parents 493bcb69166c
children
rev   line source
Daniel@0 1 /*
Daniel@0 2
Daniel@0 3 Quicksand 1.4
Daniel@0 4
Daniel@0 5 Reorder and filter items with a nice shuffling animation.
Daniel@0 6
Daniel@0 7 Copyright (c) 2010 Jacek Galanciak (razorjack.net) and agilope.com
Daniel@0 8 Big thanks for Piotr Petrus (riddle.pl) for deep code review and wonderful docs & demos.
Daniel@0 9
Daniel@0 10 Dual licensed under the MIT and GPL version 2 licenses.
Daniel@0 11 http://github.com/jquery/jquery/blob/master/MIT-LICENSE.txt
Daniel@0 12 http://github.com/jquery/jquery/blob/master/GPL-LICENSE.txt
Daniel@0 13
Daniel@0 14 Project site: http://razorjack.net/quicksand
Daniel@0 15 Github site: http://github.com/razorjack/quicksand
Daniel@0 16
Daniel@0 17 */
Daniel@0 18
Daniel@0 19 (function($) {
Daniel@0 20
Daniel@0 21 var cloneWithCanvases = function(jqueryObject) {
Daniel@0 22 var clonedJqueryObject = jqueryObject.clone();
Daniel@0 23 var canvases = jqueryObject.find('canvas');
Daniel@0 24 if (canvases.length) {
Daniel@0 25 var clonedCanvases = clonedJqueryObject.find('canvas');
Daniel@0 26 clonedCanvases.each(function(index) {
Daniel@0 27 var context = this.getContext('2d');
Daniel@0 28 context.drawImage(canvases.get(index), 0, 0);
Daniel@0 29 });
Daniel@0 30 }
Daniel@0 31 return clonedJqueryObject;
Daniel@0 32 };
Daniel@0 33
Daniel@0 34 $.fn.quicksand = function(collection, customOptions) {
Daniel@0 35 var options = {
Daniel@0 36 duration : 750,
Daniel@0 37 easing : 'swing',
Daniel@0 38 attribute : 'data-id', // attribute to recognize same items within source and dest
Daniel@0 39 adjustHeight : 'auto', // 'dynamic' animates height during shuffling (slow), 'auto' adjusts it
Daniel@0 40 // before or after the animation, false leaves height constant
Daniel@0 41 adjustWidth : 'auto', // 'dynamic' animates width during shuffling (slow),
Daniel@0 42 // 'auto' adjusts it before or after the animation, false leaves width constant
Daniel@0 43 useScaling : false, // enable it if you're using scaling effect
Daniel@0 44 enhancement : function(c) {}, // Visual enhacement (eg. font replacement) function for cloned elements
Daniel@0 45 selector : '> *',
Daniel@0 46 atomic : false,
Daniel@0 47 dx : 0,
Daniel@0 48 dy : 0,
Daniel@0 49 maxWidth : 0,
Daniel@0 50 retainExisting : true // disable if you want the collection of items to be replaced completely by incoming items.
Daniel@0 51 },
Daniel@0 52
Daniel@0 53 nativeScaleSupport = (function() {
Daniel@0 54 var prefixes = 'transform WebkitTransform MozTransform OTransform msTransform'.split(' '),
Daniel@0 55 el = document.createElement('div');
Daniel@0 56 for (var i = 0; i < prefixes.length; i++) {
Daniel@0 57 if (typeof el.style[prefixes[i]] != 'undefined') {
Daniel@0 58 return true;
Daniel@0 59 }
Daniel@0 60 }
Daniel@0 61 return false;
Daniel@0 62 })();
Daniel@0 63
Daniel@0 64 $.extend(options, customOptions);
Daniel@0 65
Daniel@0 66 // Can the browser do scaling?
Daniel@0 67 if (!nativeScaleSupport || (typeof ($.fn.scale) == 'undefined')) {
Daniel@0 68 options.useScaling = false;
Daniel@0 69 }
Daniel@0 70
Daniel@0 71 var callbackFunction;
Daniel@0 72 if (typeof (arguments[1]) == 'function') {
Daniel@0 73 callbackFunction = arguments[1];
Daniel@0 74 } else if (typeof (arguments[2] == 'function')) {
Daniel@0 75 callbackFunction = arguments[2];
Daniel@0 76 }
Daniel@0 77
Daniel@0 78 return this.each(function(i) {
Daniel@0 79 var val;
Daniel@0 80 var animationQueue = []; // used to store all the animation params before starting the animation;
Daniel@0 81 // solves initial animation slowdowns
Daniel@0 82 var $collection;
Daniel@0 83 if (typeof(options.attribute) == 'function') {
Daniel@0 84 $collection = $(collection);
Daniel@0 85 } else {
Daniel@0 86 $collection = cloneWithCanvases($(collection).filter('[' + options.attribute + ']')); // destination (target) collection
Daniel@0 87 }
Daniel@0 88 var $sourceParent = $(this); // source, the visible container of source collection
Daniel@0 89 var sourceHeight = $(this).css('height'); // used to keep height and document flow during the animation
Daniel@0 90 var sourceWidth = $(this).css('width'); // used to keep width and document flow during the animation
Daniel@0 91 var destHeight, destWidth;
Daniel@0 92 var adjustHeightOnCallback = false;
Daniel@0 93 var adjustWidthOnCallback = false;
Daniel@0 94 var offset = $($sourceParent).offset(); // offset of visible container, used in animation calculations
Daniel@0 95 var offsets = []; // coordinates of every source collection item
Daniel@0 96 var $source = $(this).find(options.selector); // source collection items
Daniel@0 97 var width = $($source).innerWidth(); // need for the responsive design
Daniel@0 98
Daniel@0 99 // Replace the collection and quit if IE6
Daniel@0 100 if (navigator.userAgent.match(/msie [6]/i)) {
Daniel@0 101 $sourceParent.html('').append($collection);
Daniel@0 102 return;
Daniel@0 103 }
Daniel@0 104
Daniel@0 105 // Gets called when any animation is finished
Daniel@0 106 var postCallbackPerformed = 0; // prevents the function from being called more than one time
Daniel@0 107 var postCallback = function() {
Daniel@0 108 $(this).css('margin', '').css('position', '').css('top', '').css('left', '').css('opacity', '');
Daniel@0 109 if (!postCallbackPerformed) {
Daniel@0 110 postCallbackPerformed = 1;
Daniel@0 111
Daniel@0 112 if (!options.atomic) {
Daniel@0 113 // hack: used to be: $sourceParent.html($dest.html());
Daniel@0 114 // put target HTML into visible source container
Daniel@0 115 // but new webkit builds cause flickering when replacing the collections
Daniel@0 116 var $toDelete = $sourceParent.find(options.selector);
Daniel@0 117 if (!options.retainExisting) {
Daniel@0 118 $sourceParent.prepend($dest.find(options.selector));
Daniel@0 119 $toDelete.remove();
Daniel@0 120 } else {
Daniel@0 121 // Avoid replacing elements because we may have already altered items in significant
Daniel@0 122 // ways and it would be bad to have to do it again. (i.e. lazy load images)
Daniel@0 123 // But $dest holds the correct ordering. So we must re-sequence items in $sourceParent to match.
Daniel@0 124 var $keepElements = $([]);
Daniel@0 125 $dest.find(options.selector).each(function(i) {
Daniel@0 126 var $matchedElement = $([]);
Daniel@0 127 if (typeof (options.attribute) == 'function') {
Daniel@0 128 var val = options.attribute($(this));
Daniel@0 129 $toDelete.each(function() {
Daniel@0 130 if (options.attribute(this) == val) {
Daniel@0 131 $matchedElement = $(this);
Daniel@0 132 return false;
Daniel@0 133 }
Daniel@0 134 });
Daniel@0 135 } else {
Daniel@0 136 $matchedElement = $toDelete.filter(
Daniel@0 137 '[' + options.attribute + '="'+
Daniel@0 138 $(this).attr(options.attribute) + '"]');
Daniel@0 139 }
Daniel@0 140 if ($matchedElement.length > 0) {
Daniel@0 141 // There is a matching element in the $toDelete list and in $dest
Daniel@0 142 // list, so make sure it is in the right location within $sourceParent
Daniel@0 143 // and put it in the list of elements we need to not delete.
Daniel@0 144 $keepElements = $keepElements.add($matchedElement);
Daniel@0 145 if (i === 0) {
Daniel@0 146 $sourceParent.prepend($matchedElement);
Daniel@0 147 } else {
Daniel@0 148 $matchedElement.insertAfter($sourceParent.find(options.selector).get(i - 1));
Daniel@0 149 }
Daniel@0 150 }
Daniel@0 151 });
Daniel@0 152 // Remove whatever is remaining from the DOM
Daniel@0 153 $toDelete.not($keepElements).remove();
Daniel@0 154 }
Daniel@0 155
Daniel@0 156 if (adjustHeightOnCallback) {
Daniel@0 157 $sourceParent.css('height', destHeight);
Daniel@0 158 }
Daniel@0 159 if (adjustWidthOnCallback) {
Daniel@0 160 $sourceParent.css('width', sourceWidth);
Daniel@0 161 }
Daniel@0 162 }
Daniel@0 163 options.enhancement($sourceParent); // Perform custom visual enhancements on a newly replaced collection
Daniel@0 164 if (typeof callbackFunction == 'function') {
Daniel@0 165 callbackFunction.call(this);
Daniel@0 166 }
Daniel@0 167 }
Daniel@0 168
Daniel@0 169 if (false === options.adjustHeight) {
Daniel@0 170 $sourceParent.css('height', 'auto');
Daniel@0 171 }
Daniel@0 172
Daniel@0 173 if (false === options.adjustWidth) {
Daniel@0 174 $sourceParent.css('width', 'auto');
Daniel@0 175 }
Daniel@0 176 };
Daniel@0 177
Daniel@0 178 // Position: relative situations
Daniel@0 179 var $correctionParent = $sourceParent.offsetParent();
Daniel@0 180 var correctionOffset = $correctionParent.offset();
Daniel@0 181 if ($correctionParent.css('position') == 'relative') {
Daniel@0 182 if ($correctionParent.get(0).nodeName.toLowerCase() != 'body') {
Daniel@0 183 correctionOffset.top += (parseFloat($correctionParent.css('border-top-width')) || 0);
Daniel@0 184 correctionOffset.left += (parseFloat($correctionParent.css('border-left-width')) || 0);
Daniel@0 185 }
Daniel@0 186 } else {
Daniel@0 187 correctionOffset.top -= (parseFloat($correctionParent.css('border-top-width')) || 0);
Daniel@0 188 correctionOffset.left -= (parseFloat($correctionParent.css('border-left-width')) || 0);
Daniel@0 189 correctionOffset.top -= (parseFloat($correctionParent.css('margin-top')) || 0);
Daniel@0 190 correctionOffset.left -= (parseFloat($correctionParent.css('margin-left')) || 0);
Daniel@0 191 }
Daniel@0 192
Daniel@0 193 // perform custom corrections from options (use when Quicksand fails to detect proper correction)
Daniel@0 194 if (isNaN(correctionOffset.left)) {
Daniel@0 195 correctionOffset.left = 0;
Daniel@0 196 }
Daniel@0 197 if (isNaN(correctionOffset.top)) {
Daniel@0 198 correctionOffset.top = 0;
Daniel@0 199 }
Daniel@0 200
Daniel@0 201 correctionOffset.left -= options.dx;
Daniel@0 202 correctionOffset.top -= options.dy;
Daniel@0 203
Daniel@0 204 // keeps nodes after source container, holding their position
Daniel@0 205 $sourceParent.css('height', $(this).height());
Daniel@0 206 $sourceParent.css('width', $(this).width());
Daniel@0 207
Daniel@0 208 // get positions of source collections
Daniel@0 209 $source.each(function(i) {
Daniel@0 210 offsets[i] = $(this).offset();
Daniel@0 211 });
Daniel@0 212
Daniel@0 213 // stops previous animations on source container
Daniel@0 214 $(this).stop();
Daniel@0 215 var dx = 0;
Daniel@0 216 var dy = 0;
Daniel@0 217 $source.each(function(i) {
Daniel@0 218 $(this).stop(); // stop animation of collection items
Daniel@0 219 var rawObj = $(this).get(0);
Daniel@0 220 if (rawObj.style.position == 'absolute') {
Daniel@0 221 dx = -options.dx;
Daniel@0 222 dy = -options.dy;
Daniel@0 223 } else {
Daniel@0 224 dx = options.dx;
Daniel@0 225 dy = options.dy;
Daniel@0 226 }
Daniel@0 227
Daniel@0 228 rawObj.style.position = 'absolute';
Daniel@0 229 rawObj.style.margin = '0';
Daniel@0 230
Daniel@0 231 if (!options.adjustWidth) {
Daniel@0 232 rawObj.style.width = (width + 'px'); // sets the width to the current element
Daniel@0 233 // with even if it has been changed
Daniel@0 234 // by a responsive design
Daniel@0 235 }
Daniel@0 236
Daniel@0 237 rawObj.style.top = (offsets[i].top- parseFloat(rawObj.style.marginTop) - correctionOffset.top + dy) + 'px';
Daniel@0 238 rawObj.style.left = (offsets[i].left- parseFloat(rawObj.style.marginLeft) - correctionOffset.left + dx) + 'px';
Daniel@0 239
Daniel@0 240 if (options.maxWidth > 0 && offsets[i].left > options.maxWidth) {
Daniel@0 241 rawObj.style.display = 'none';
Daniel@0 242 }
Daniel@0 243 });
Daniel@0 244
Daniel@0 245 // create temporary container with destination collection
Daniel@0 246 var $dest = cloneWithCanvases($($sourceParent));
Daniel@0 247 var rawDest = $dest.get(0);
Daniel@0 248 rawDest.innerHTML = '';
Daniel@0 249 rawDest.setAttribute('id', '');
Daniel@0 250 rawDest.style.height = 'auto';
Daniel@0 251 rawDest.style.width = $sourceParent.width() + 'px';
Daniel@0 252 $dest.append($collection);
Daniel@0 253 // Inserts node into HTML. Note that the node is under visible source container in the exactly same position
Daniel@0 254 // The browser render all the items without showing them (opacity: 0.0) No offset calculations are needed,
Daniel@0 255 // the browser just extracts position from underlayered destination items and sets animation to destination positions.
Daniel@0 256 $dest.insertBefore($sourceParent);
Daniel@0 257 $dest.css('opacity', 0.0);
Daniel@0 258 rawDest.style.zIndex = -1;
Daniel@0 259
Daniel@0 260 rawDest.style.margin = '0';
Daniel@0 261 rawDest.style.position = 'absolute';
Daniel@0 262 rawDest.style.top = offset.top - correctionOffset.top + 'px';
Daniel@0 263 rawDest.style.left = offset.left - correctionOffset.left + 'px';
Daniel@0 264
Daniel@0 265 if (options.adjustHeight === 'dynamic') {
Daniel@0 266 // If destination container has different height than source container the height can be animated,
Daniel@0 267 // adjusting it to destination height
Daniel@0 268 $sourceParent.animate({ height : $dest.height() }, options.duration, options.easing);
Daniel@0 269 } else if (options.adjustHeight === 'auto') {
Daniel@0 270 destHeight = $dest.height();
Daniel@0 271 if (parseFloat(sourceHeight) < parseFloat(destHeight)) {
Daniel@0 272 // Adjust the height now so that the items don't move out of the container
Daniel@0 273 $sourceParent.css('height', destHeight);
Daniel@0 274 } else {
Daniel@0 275 // Adjust later, on callback
Daniel@0 276 adjustHeightOnCallback = true;
Daniel@0 277 }
Daniel@0 278 }
Daniel@0 279
Daniel@0 280 if (options.adjustWidth === 'dynamic') {
Daniel@0 281 // If destination container has different width than source container the width can be animated,
Daniel@0 282 // adjusting it to destination width
Daniel@0 283 $sourceParent.animate({ width : $dest.width() }, options.duration, options.easing);
Daniel@0 284 } else if (options.adjustWidth === 'auto') {
Daniel@0 285 destWidth = $dest.width();
Daniel@0 286 if (parseFloat(sourceWidth) < parseFloat(destWidth)) {
Daniel@0 287 // Adjust the height now so that the items don't move out of the container
Daniel@0 288 $sourceParent.css('width', destWidth);
Daniel@0 289 } else {
Daniel@0 290 // Adjust later, on callback
Daniel@0 291 adjustWidthOnCallback = true;
Daniel@0 292 }
Daniel@0 293 }
Daniel@0 294
Daniel@0 295 // Now it's time to do shuffling animation. First of all, we need to identify same elements within
Daniel@0 296 // source and destination collections
Daniel@0 297 $source.each(function(i) {
Daniel@0 298 var destElement = [];
Daniel@0 299 if (typeof (options.attribute) == 'function') {
Daniel@0 300 val = options.attribute($(this));
Daniel@0 301 $collection.each(function() {
Daniel@0 302 if (options.attribute(this) == val) {
Daniel@0 303 destElement = $(this);
Daniel@0 304 return false;
Daniel@0 305 }
Daniel@0 306 });
Daniel@0 307 } else {
Daniel@0 308 destElement = $collection.filter('[' + options.attribute + '="' + $(this).attr(options.attribute) + '"]');
Daniel@0 309 }
Daniel@0 310 if (destElement.length) {
Daniel@0 311 // The item is both in source and destination collections. It it's under different position, let's move it
Daniel@0 312 if (!options.useScaling) {
Daniel@0 313 animationQueue.push({
Daniel@0 314 element : $(this), dest : destElement,
Daniel@0 315 style : {
Daniel@0 316 top : $(this).offset().top,
Daniel@0 317 left : $(this).offset().left,
Daniel@0 318 opacity : ""
Daniel@0 319 },
Daniel@0 320 animation : {
Daniel@0 321 top : destElement.offset().top - correctionOffset.top,
Daniel@0 322 left : destElement.offset().left - correctionOffset.left,
Daniel@0 323 opacity : 1.0
Daniel@0 324 }
Daniel@0 325 });
Daniel@0 326 } else {
Daniel@0 327 animationQueue.push({
Daniel@0 328 element : $(this), dest : destElement,
Daniel@0 329 style : {
Daniel@0 330 top : $(this).offset().top,
Daniel@0 331 left : $(this).offset().left,
Daniel@0 332 opacity : ""
Daniel@0 333 },
Daniel@0 334 animation : {
Daniel@0 335 top : destElement.offset().top - correctionOffset.top,
Daniel@0 336 left : destElement.offset().left - correctionOffset.left,
Daniel@0 337 opacity : 1.0,
Daniel@0 338 scale : '1.0'
Daniel@0 339 }
Daniel@0 340 });
Daniel@0 341 }
Daniel@0 342 } else {
Daniel@0 343 // The item from source collection is not present in destination collections. Let's remove it
Daniel@0 344 if (!options.useScaling) {
Daniel@0 345 animationQueue.push({
Daniel@0 346 element : $(this),
Daniel@0 347 style : {
Daniel@0 348 top : $(this).offset().top,
Daniel@0 349 left : $(this).offset().left,
Daniel@0 350 opacity : ""
Daniel@0 351 },
Daniel@0 352 animation : {
Daniel@0 353 opacity : '0.0'
Daniel@0 354 }
Daniel@0 355 });
Daniel@0 356 } else {
Daniel@0 357 animationQueue.push({
Daniel@0 358 element : $(this),
Daniel@0 359 animation : {
Daniel@0 360 opacity : '0.0',
Daniel@0 361 style : {
Daniel@0 362 top : $(this).offset().top,
Daniel@0 363 left : $(this).offset().left,
Daniel@0 364 opacity : ""
Daniel@0 365 },
Daniel@0 366 scale : '0.0'
Daniel@0 367 }
Daniel@0 368 });
Daniel@0 369 }
Daniel@0 370 }
Daniel@0 371 });
Daniel@0 372
Daniel@0 373 $collection.each(function(i) {
Daniel@0 374 // Grab all items from target collection not present in visible source collection
Daniel@0 375 var sourceElement = [];
Daniel@0 376 var destElement = [];
Daniel@0 377 if (typeof (options.attribute) == 'function') {
Daniel@0 378 val = options.attribute($(this));
Daniel@0 379 $source.each(function() {
Daniel@0 380 if (options.attribute(this) == val) {
Daniel@0 381 sourceElement = $(this);
Daniel@0 382 return false;
Daniel@0 383 }
Daniel@0 384 });
Daniel@0 385
Daniel@0 386 $collection.each(function() {
Daniel@0 387 if (options.attribute(this) == val) {
Daniel@0 388 destElement = $(this);
Daniel@0 389 return false;
Daniel@0 390 }
Daniel@0 391 });
Daniel@0 392 } else {
Daniel@0 393 sourceElement = $source.filter('[' + options.attribute + '="' + $(this).attr(options.attribute) + '"]');
Daniel@0 394 destElement = $collection.filter('[' + options.attribute + '="' + $(this).attr(options.attribute) + '"]');
Daniel@0 395 }
Daniel@0 396
Daniel@0 397 var animationOptions;
Daniel@0 398 if (sourceElement.length === 0 && destElement.length > 0) {
Daniel@0 399
Daniel@0 400 // No such element in source collection...
Daniel@0 401 if (!options.useScaling) {
Daniel@0 402 animationOptions = {opacity : '1.0'};
Daniel@0 403 } else {
Daniel@0 404 animationOptions = {opacity : '1.0', scale : '1.0'};
Daniel@0 405 }
Daniel@0 406
Daniel@0 407 // Let's create it
Daniel@0 408 var d = cloneWithCanvases(destElement);
Daniel@0 409 var rawDestElement = d.get(0);
Daniel@0 410 rawDestElement.style.position = 'absolute';
Daniel@0 411 rawDestElement.style.margin = '0';
Daniel@0 412
Daniel@0 413 if (!options.adjustWidth) {
Daniel@0 414 // sets the width to the current element with even if it has been changed by a responsive design
Daniel@0 415 rawDestElement.style.width = width + 'px';
Daniel@0 416 }
Daniel@0 417
Daniel@0 418 rawDestElement.style.top = destElement.offset().top - correctionOffset.top + 'px';
Daniel@0 419 rawDestElement.style.left = destElement.offset().left - correctionOffset.left + 'px';
Daniel@0 420
Daniel@0 421 d.css('opacity', 0.0); // IE
Daniel@0 422
Daniel@0 423 if (options.useScaling) {
Daniel@0 424 d.scale(0.0);
Daniel@0 425 }
Daniel@0 426 d.appendTo($sourceParent);
Daniel@0 427
Daniel@0 428 if (options.maxWidth === 0 || destElement.offset().left < options.maxWidth) {
Daniel@0 429 animationQueue.push({element : $(d), dest : destElement,animation : animationOptions});
Daniel@0 430 }
Daniel@0 431 }
Daniel@0 432 });
Daniel@0 433
Daniel@0 434 $dest.remove();
Daniel@0 435 if (!options.atomic) {
Daniel@0 436 options.enhancement($sourceParent); // Perform custom visual enhancements during the animation
Daniel@0 437 for (i = 0; i < animationQueue.length; i++) {
Daniel@0 438 animationQueue[i].element.animate(animationQueue[i].animation, options.duration, options.easing, postCallback);
Daniel@0 439 }
Daniel@0 440 } else {
Daniel@0 441 $toDelete = $sourceParent.find(options.selector);
Daniel@0 442 $sourceParent.prepend($dest.find(options.selector));
Daniel@0 443 for (i = 0; i < animationQueue.length; i++) {
Daniel@0 444 if (animationQueue[i].dest && animationQueue[i].style) {
Daniel@0 445 var destElement = animationQueue[i].dest;
Daniel@0 446 var destOffset = destElement.offset();
Daniel@0 447
Daniel@0 448 destElement.css({
Daniel@0 449 position : 'relative',
Daniel@0 450 top : (animationQueue[i].style.top - destOffset.top),
Daniel@0 451 left : (animationQueue[i].style.left - destOffset.left)
Daniel@0 452 });
Daniel@0 453
Daniel@0 454 destElement.animate({top : "0", left : "0"},
Daniel@0 455 options.duration,
Daniel@0 456 options.easing,
Daniel@0 457 postCallback);
Daniel@0 458 } else {
Daniel@0 459 animationQueue[i].element.animate(animationQueue[i].animation,
Daniel@0 460 options.duration,
Daniel@0 461 options.easing,
Daniel@0 462 postCallback);
Daniel@0 463 }
Daniel@0 464 }
Daniel@0 465 $toDelete.remove();
Daniel@0 466 }
Daniel@0 467 });
Daniel@0 468 };
Daniel@0 469 })(jQuery);