UNPKG

doevisualizations

Version:

Data Visualization Library based on RequireJS and D3.js (v4+)

345 lines (303 loc) 9.91 kB
steal('jquery', function ($) { // Overwrites `jQuery.fn.animate` to use CSS 3 animations if possible var // The global animation counter animationNum = 0, // The stylesheet for our animations styleSheet = null, // The animation cache cache = [], // Stores the browser properties like transition end event name and prefix browser = null, // Store the original $.fn.animate oldanimate = $.fn.animate, // Return the stylesheet, create it if it doesn't exists getStyleSheet = function () { if(!styleSheet) { var style = document.createElement('style'); style.setAttribute("type", "text/css"); style.setAttribute("media", "screen"); document.getElementsByTagName('head')[0].appendChild(style); if (!window.createPopup) { /* For Safari */ style.appendChild(document.createTextNode('')); } styleSheet = style.sheet; } return styleSheet; }, //removes an animation rule from a sheet removeAnimation = function (sheet, name) { for (var j = sheet.cssRules.length - 1; j >= 0; j--) { var rule = sheet.cssRules[j]; // 7 means the keyframe rule if (rule.type === 7 && rule.name == name) { sheet.deleteRule(j) return; } } }, // Returns whether the animation should be passed to the original $.fn.animate. passThrough = function (props, ops, easing, callback) { var nonElement = !(this[0] && this[0].nodeType), isInline = !nonElement && $(this).css("display") === "inline" && $(this).css("float") === "none", browser = getBrowser(); for (var name in props) { // jQuery does something with these values if (props[name] == 'show' || props[name] == 'hide' || props[name] == 'toggle' // Arrays for individual easing || $.isArray(props[name]) // Negative values not handled the same || props[name] < 0 // unit-less value || name == 'zIndex' || name == 'z-index' || name == 'scrollTop' || name == 'scrollLeft' ) { return true; } } return props.jquery === true || browser === null || browser.prefix === '-o-' || // Animating empty properties $.isEmptyObject(props) || // We can't do custom easing (easing || easing && typeof easing == 'string') || // Second parameter is an object - we can only handle primitives $.isPlainObject(ops) || // Inline and non elements isInline || nonElement; }, // Gets a CSS number (with px added as the default unit if the value is a number) cssValue = function(origName, value) { if (typeof value === "number" && !$.cssNumber[ origName ]) { return value += "px"; } return value; }, // Feature detection borrowed by http://modernizr.com/ getBrowser = function(){ if(!browser) { var t, el = document.createElement('fakeelement'), transitions = { 'transition': { transitionEnd : 'transitionend', prefix : '' }, 'MozTransition': { transitionEnd : 'animationend', prefix : '-moz-' }, 'WebkitTransition': { transitionEnd : 'webkitTransitionEnd', prefix : '-webkit-' }, 'OTransition': { transitionEnd : 'oTransitionEnd', prefix : '-o-' } } for(t in transitions){ if( t in el.style ){ browser = transitions[t]; } } } return browser; }, // Properties that Firefox can't animate if set to 'auto': // https://bugzilla.mozilla.org/show_bug.cgi?id=571344 // Provides a converter that returns the actual value ffProps = { top : function(el) { return el.position().top; }, left : function(el) { return el.position().left; }, width : function(el) { return el.width(); }, height : function(el) { return el.height(); }, fontSize : function(el) { return '1em'; } }, // Add browser specific prefix addPrefix = function(properties) { var result = {}; $.each(properties, function(name, value) { result[getBrowser().prefix + name] = value; }); return result; }, // Returns the animation name for a given style. It either uses a cached // version or adds it to the stylesheet, removing the oldest style if the // cache has reached a certain size. getAnimation = function(style) { var sheet, name, last; // Look up the cached style, set it to that name and reset age if found // increment the age for any other animation $.each(cache, function(i, animation) { if(style === animation.style) { name = animation.name; animation.age = 0; } else { animation.age += 1; } }); if(!name) { // Add a new style sheet = getStyleSheet(); name = "jquerypp_animation_" + (animationNum++); // get the last sheet and insert this rule into it sheet.insertRule("@" + getBrowser().prefix + "keyframes " + name + ' ' + style, (sheet.cssRules && sheet.cssRules.length) || 0); cache.push({ name : name, style : style, age : 0 }); // Sort the cache by age cache.sort(function(first, second) { return first.age - second.age; }); // Remove the last (oldest) item from the cache if it has more than 20 items if(cache.length > 20) { last = cache.pop(); removeAnimation(sheet, last.name); } } return name; }; /** * @parent jQuery.animate * @function jQuery.fn.animate * @signature $(element).animate(options) * @hide * * Animate CSS properties using native CSS animations, if possible. * Uses the original [$.fn.animate()](http://api.$.com/animate/) otherwise. * * @param {Object} props The CSS properties to animate * @param {Integer|String|Object} [speed=400] The animation duration in ms. * Will use $.fn.animate if a string or object is passed * @param {Function} [callback] A callback to execute once the animation is complete * @return {jQuery} The jQuery element */ $.fn.animate = function (props, speed, easing, callback) { //default to normal animations if browser doesn't support them if (passThrough.apply(this, arguments)) { return oldanimate.apply(this, arguments); } var optall = $.speed(speed, easing, callback), overflow = []; // if we are animating height and width properties, set overflow to hidden, and save // the previous overflow information to replace with when done. if("height" in props || "width" in props) { overflow = [this[0].style.overflow, this[0].style.overflowX, this[0].style.overflowY]; this.css('overflow', 'hidden'); } // Add everything to the animation queue this.queue(optall.queue, function(done) { var //current CSS values current, // The list of properties passed properties = [], to = "", prop, self = $(this), duration = optall.duration, //the animation keyframe name animationName, // The key used to store the animation hook dataKey, //the text for the keyframe style = "{ from {", // The animation end event handler. // Will be called both on animation end and after calling .stop() animationEnd = function (currentCSS, exec) { // As long as we don't stop mid animation, then we will replace // the overflow values of the element being animated. if(!exec) { self[0].style.overflow = overflow[0]; self[0].style.overflowX = overflow[1]; self[0].style.overflowY = overflow[2]; } else { self.css('overflow', ''); } self.css(currentCSS); self.css(addPrefix({ "animation-duration" : "", "animation-name" : "", "animation-fill-mode" : "", "animation-play-state" : "" })); // Call the original callback if ($.isFunction(optall.old) && exec) { // Call success, pass the DOM element as the this reference optall.old.call(self[0], true) } $.removeData(self, dataKey, true); }, finishAnimation = function() { // Call animationEnd using the passed properties animationEnd(props, true); done(); }; for(prop in props) { properties.push(prop); } if(getBrowser().prefix === '-moz-' || /Edge\/\d+/.test(navigator.userAgent)) { // Normalize 'auto' properties in FF // This is also needed in Edge (tested in 13) $.each(properties, function(i, prop) { var converter = ffProps[$.camelCase(prop)]; if(converter && self.css(prop) == 'auto') { self.css(prop, converter(self)); } }); } // Use $.styles current = self.css.apply(self, properties); $.each(properties, function(i, cur) { // Convert a camelcased property name var name = cur.replace(/([A-Z]|^ms)/g, "-$1" ).toLowerCase(); style += name + " : " + cssValue(cur, current[cur]) + "; "; to += name + " : " + cssValue(cur, props[cur]) + "; "; }); style += "} to {" + to + " }}"; animationName = getAnimation(style); dataKey = animationName + '.run'; // Add a hook which will be called when the animation stops $._data(this, dataKey, { stop : function(gotoEnd) { // Pause the animation self.css(addPrefix({ 'animation-play-state' : 'paused' })); // Unbind the animation end handler self.off(getBrowser().transitionEnd, finishAnimation); if(!gotoEnd) { // We were told not to finish the animation // Call animationEnd but set the CSS to the current computed style animationEnd(self.styles.apply(self, properties), false); } else { // Finish animaion animationEnd(props, true); } } }); // set this element to point to that animation self.css(addPrefix({ "animation-duration" : duration + "ms", "animation-name" : animationName, "animation-fill-mode": "forwards" })); // Attach the transition end event handler to run only once self.one(getBrowser().transitionEnd, finishAnimation); }); return this; }; return $; });