zepto.js Source Code Analysis
Category Programming Technology
Zepto is a lightweight JavaScript library for modern advanced browsers, with an API similar to jQuery. If you can use jQuery, you can also use Zepto.
Zepto Chinese Manual:https://www.tutorialpro.org/manual/zeptojs.html
/* Zepto v1.0-1-ga3cab6c - polyfill zepto detect event ajax form fx - zeptojs.com/license */
;(function(undefined) {
if (String.prototype.trim === undefined) // fix for iOS 3.2
String.prototype.trim = function() {
return this.replace(/^\s+|\s+$/g, '')
}
// For iOS 3.x
// from https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/reduce
// This method acts as an accumulative processing function, using the result of the previous data processing as input for the next processing.
// For example, [1,2,3,4,].reduce(function(x,y){ return x+y}); ==> ((1+2)+3)+4,
if (Array.prototype.reduce === undefined) Array.prototype.reduce = function(fun) {
if (this === void 0 || this === null) throw new TypeError()
var t = Object(this),
len = t.length >>> 0,
k = 0,
accumulator
if (typeof fun != 'function') throw new TypeError()
if (len == 0 && arguments.length == 1) throw new TypeError()
// Get initial value
if (arguments.length >= 2) accumulator = arguments[1] // If the number of arguments is greater than 2, use the second argument as the initial value
else do {
if (k in t) {
accumulator = t[k++] // Otherwise, use the first data of the array as the initial value
break
}
if (++k >= len) throw new TypeError() // Under what circumstances would this be executed?
} while (true)
// Traverse the array, passing the result of the previous processing into the processing function for cumulative processing
while (k < len) {
if (k in t) accumulator = fun.call(undefined, accumulator, t[k], k, t)
k++
}
return accumulator
}
})()
var Zepto = (function() {
var undefined, key, $, classList, emptyArray = [],
slice = emptyArray.slice,
filter = emptyArray.filter,
document = window.document,
elementDisplay = {}, classCache = {},
getComputedStyle = document.defaultView.getComputedStyle,
// CSS properties that do not require the 'px' unit when set
cssNumber = {
'column-count': 1,
'columns': 1,
'font-weight': 1,
'line-height': 1,
'opacity': 1,
'z-index': 1,
'zoom': 1
},
// Regular expression for HTML code snippets
fragmentRE = /^\s*<(\w+|!)[^>]*>/,
// Matches tags that are not standalone closed tags, like writing <div></div> as <div/>
tagExpanderRE = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
// Root node
rootNodeRE = /^(?:body|html)$/i,
// Method names that require get and set functions
// special attributes that should be get/set via method calls
methodAttributes = ['val', 'css', 'html', 'text', 'data', 'width', 'height', 'offset'],
// Operations on adjacent nodes
adjacencyOperators = ['after', 'prepend', 'before', 'append'],
table = document.createElement('table'),
tableRow = document.createElement('tr'),
// Used when setting innerHTML for tr, tbody, thead, tfoot, td, th, using their parent elements as containers for the HTML string
containers = {
'tr': document.createElement('tbody'),
'tbody': table,
'thead': table,
'tfoot': table,
'td': tableRow,
'th': tableRow,
'*': document.createElement('div')
},
// States when the DOM is ready
readyRE = /complete|loaded|interactive/,
// Regular expression for class selectors
classSelectorRE = /^\.([\w-]+)$/,
// Regular expression for id selectors
idSelectorRE = /^#([\w-]*)$/,
// Regular expression for DOM tags
tagSelectorRE = /^[\w-]+$/,
class2type = {},
toString = class2type.toString,
zepto = {},
camelize, uniq,
tempParent = document.createElement('div');
// Check if an element matches a given selector
zepto.matches = function(element, selector) {
if (!element || element.nodeType !== 1) return false
// Use the browser-provided MatchesSelector method
var matchesSelector = element.webkitMatchesSelector || element.mozMatchesSelector || element.oMatchesSelector || element.matchesSelector
if (matchesSelector) return matchesSelector.call(element, selector);
// If the browser does not support MatchesSelector, place the node in a temporary div
// and then search for the node set under this div using the selector, and check if the given element is in the node set. If it is, return a non-zero (non-false) number.
// fall back to performing a selector:
var match, parent = element.parentNode, temp = !parent
// If element has no parent node, insert it into a temporary div
if (temp) (parent = tempParent).appendChild(element)
// Search for the selector match result under parent as context, and get the index of element in the result set, which is -1 if not present, then convert ~-1 to 0, and return a non-zero value if present
match = ~zepto.qsa(parent, selector).indexOf(element)
// Remove the inserted node
temp && tempParent.removeChild(element)
return match
}
// Get the type of an object
function type(obj) {
// Return 'null' or 'undefined' directly if obj is null or undefined
return obj == null ? String(obj) : class2type[toString.call(obj)] || "object"
}
function isFunction(value) {
return type(value) == "function"
}
function isWindow(obj) {
return obj != null && obj == obj.window
}
function isDocument(obj) {
return obj != null && obj.nodeType == obj.DOCUMENT_NODE
}
function isObject(obj) {
return type(obj) == "object"
}
// Returns true for objects defined by literal or new Object, returns false for objects created with new Object with arguments
// See http://snandy.iteye.com/blog/663245 for reference
function isPlainObject(obj) {
return isObject(obj) && !isWindow(obj) && obj.__proto__ == Object.prototype
}
function isArray(value) {
return value instanceof Array
}
// Array-like, such as nodeList. This only does a simple check. If an object has a length property with a numeric value, it will also return true.
function likeArray(obj) {
return typeof obj.length == 'number'
}
// Clears the given parameters of null or undefined, note that 0 == null, '' == null is false
function compact(array) { return filter.call(array, function(item) { return item != null }) } // Similar to obtaining a copy of an array
function flatten(array) { return array.length > 0 ? $.fn.concat.apply([], array) : array } // Converts a string to camelCase format camelize = function(str) { return str.replace(/-+(.)?/g, function(match, chr) { return chr ? chr.toUpperCase() : '' }) } // Formats the string into a hyphen-joined form, typically used for style attributes like border-width
function dasherize(str) { return str.replace(/::/g, '/') // Replaces :: with / .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2') // Inserts _ between uppercase and lowercase characters, uppercase first, e.g., AAAbb becomes AA_Abb .replace(/([a-z\d])([A-Z])/g, '$1_$2') // Inserts _ between lowercase or digit and uppercase characters, lowercase or digit first, e.g., bbbAaa becomes bbb_Aaa .replace(/_/g, '-') // Replaces _ with - .toLowerCase() // Converts to lowercase } // Removes duplicates from the array, if the position of the item in the array does not match the loop index, it indicates a duplicate value uniq = function(array) { return filter.call(array, function(item, idx) { return array.indexOf(item) == idx }) }
// Generates a regular expression from the given parameters
function classRE(name) { // classCache, caches regular expressions return name in classCache ? classCache[name] : (classCache[name] = new RegExp('(^|\s)' + name + '(\s|$)')) } // Adds 'px' unit to the required style values, except for those specified in cssNumber
function maybeAddPx(name, value) { return (typeof value == "number" && !cssNumber[dasherize(name)]) ? value + "px" : value } // Retrieves the default display property of a node
function defaultDisplay(nodeName) { var element, display if (!elementDisplay[nodeName]) { // If not in cache element = document.createElement(nodeName) document.body.appendChild(element) display = getComputedStyle(element, '').getPropertyValue("display") element.parentNode.removeChild(element) display == "none" && (display = "block") // Sets display to block when it is none, not sure why elementDisplay[nodeName] = display // Caches the default display property of the element } return elementDisplay[nodeName] } // Retrieves child nodes of the specified element (excluding text nodes), Firefox does not support children, so it has to filter childNodes
function children(element) { return 'children' in element ? slice.call(element.children) : $.map(element.childNodes, function(node) { if (node.nodeType == 1) return node }) }
// $.zepto.fragment
generates DOM nodes from a given HTML string and an optional tag name
// The generated DOM nodes are returned as an array.
// This function can be overridden in plugins to make it compatible with browsers that don't support the DOM fully.
zepto.fragment = function(html, name, properties) {
// Replaces <div class="test"/> with <div class="test"></div>, a kind of fix
if (html.replace) html = html.replace(tagExpanderRE, "<$1></$2>")
// Sets the tag name for name
if (name === undefined) name = fragmentRE.test(html) && RegExp.$1
// Sets the container tag name, if not tr, tbody, thead, tfoot, td, th, then the container tag name is div
if (!(name in containers)) name = '*'
var nodes, dom, container = containers[name] // Creates a container container.innerHTML = '' + html // Puts the HTML snippet into the container // Retrieves child nodes of the container, converting the string directly into DOM nodes dom = $.each(slice.call(container.childNodes), function() { container.removeChild(this) // Deletes each node one by one }) // If properties is an object, it sets the properties for the added nodes if (isPlainObject(properties)) { nodes = $(dom) // Converts dom into a zepto object for easy access to zepto methods // Iterates through the object to set properties $.each(properties, function(key, value) { // If setting 'val', 'css', 'html', 'text', 'data', 'width', 'height', 'offset', it calls the corresponding zepto method if (methodAttributes.indexOf(key) > -1) nodeskey else nodes.attr(key, value) }) } // Returns the array of DOM nodes converted from the string, e.g., '<li></li><li></li><li></li>' becomes [li, li, li] return dom }
// $.zepto.Z
replaces the prototype of the given dom
array of nodes with $.fn
, thus providing all Zepto functions to the array.
// Note that __proto__
is not supported on Internet Explorer. This method can be overridden in plugins.
zepto.Z = function(dom, selector) {
dom = dom || []
dom.__proto__ = $.fn // Inherits all methods from $.fn by setting __proto__ to $.fn
dom.selector = selector || ''
return dom
}
// $.zepto.isZ
should return true
if the given object is a Zepto collection. This method can be overridden in plugins.
// Checks if the given parameter is a Zepto collection
zepto.isZ = function(object) {
return object instanceof zepto.Z
}
// $.zepto.init
is Zepto's counterpart to jQuery's $.fn.init
and takes a CSS selector and an optional context (and handles various special cases).
// This method can be overridden in plugins.
zepto.init = function(selector, context) {
// If nothing is given, return an empty Zepto collection
if (!selector) return zepto.Z() // No parameters, returns an empty array
// If selector is a function, execute it when the DOM is ready
else if (isFunction(selector)) return $(document).ready(selector)
// If selector is a zepto.Z instance, return it directly
else if (zepto.isZ(selector)) return selector
else {
var dom
// If selector is an array, remove null and undefined from it
if (isArray(selector)) dom = compact(selector)
// If selector is an object, note that the typeof a DOM node is also object, so it needs to be checked again inside
else if (isObject(selector))
// If it is a declared object, such as {}, copy the selector properties to a new object and put the result in an array
// If it is a DOM node, put it directly into the array
// ... (rest of the code)
dom = [isPlainObject(selector) ? $.extend({}, selector) : selector], selector = null
// If selector is an HTML fragment, convert it to DOM nodes
else if (fragmentRE.test(selector)) dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector = null
// If context is provided, find selector within context, where selector is a regular CSS selector
else if (context !== undefined) return $(context).find(selector)
// If no context is given, find selector in document, where selector is a regular CSS selector
else dom = zepto.qsa(document, selector)
// Finally, convert the query results into a zepto collection
return zepto.Z(dom, selector)
}
}
// `$` will be the base `Zepto` object. When calling this
// function just call `$.zepto.init`, which makes the implementation
// details of selecting nodes and creating Zepto collections
// patchable in plugins.
$ = function(selector, context) {
return zepto.init(selector, context)
}
// Extend, deep indicates whether to extend deeply
function extend(target, source, deep) {
for (key in source)
// If deep extension
if (deep && (isPlainObject(source[key]) || isArray(source[key]))) {
// If the data to extend is an object and the corresponding key in target is not an object
if (isPlainObject(source[key]) && !isPlainObject(target[key])) target[key] = {}
// If the data to extend is an array and the corresponding key in target is not an array
if (isArray(source[key]) && !isArray(target[key])) target[key] = []
extend(target[key], source[key], deep)
} else if (source[key] !== undefined) target[key] = source[key]
}
// Copy all but undefined properties from one or more
// objects to the `target` object.
$.extend = function(target) {
var deep, args = slice.call(arguments, 1)
if (typeof target == 'boolean') { // When the first argument is a boolean, it indicates whether to extend deeply
deep = target
target = args.shift() // target takes the second argument
}
// Iterate over the remaining arguments and extend all to target
args.forEach(function(arg) {
extend(target, arg, deep)
})
return target
}
// `$.zepto.qsa` is Zepto's CSS selector implementation which
// uses `document.querySelectorAll` and optimizes for some special cases, like `#id`.
// This method can be overridden in plugins.
zepto.qsa = function(element, selector) {
var found
// When element is document and selector is an ID selector
return (isDocument(element) && idSelectorRE.test(selector)) ?
// Directly return document.getElementById, RegExp.$1 is the ID value, return [] if no node is found
((found = element.getElementById(RegExp.$1)) ? [found] : []) :
// When element is not an element node or document, return []
(element.nodeType !== 1 && element.nodeType !== 9) ? [] :
// Otherwise, convert the obtained results to an array and return
slice.call(
// If selector is a tag name, directly call getElementsByClassName
classSelectorRE.test(selector) ? element.getElementsByClassName(RegExp.$1) :
// If selector is a tag name, directly call getElementsByTagName
tagSelectorRE.test(selector) ? element.getElementsByTagName(selector) :
// Otherwise, call querySelectorAll
element.querySelectorAll(selector))
}
// Filter results
function filtered(nodes, selector) {
return selector === undefined ? $(nodes) : $(nodes).filter(selector)
}
// Check if parent contains node
$.contains = function(parent, node) {
return parent !== node && parent.contains(node)
}
// This function plays a very important role throughout the library, handling cases where arg is a function or value
// Many functions for setting element attributes use this
function funcArg(context, arg, idx, payload) {
return isFunction(arg) ? arg.call(context, idx, payload) : arg
}
function setAttribute(node, name, value) {
// If the value to set is null or undefined, it is equivalent to deleting the attribute, otherwise set the name attribute to value
value == null ? node.removeAttribute(name) : node.setAttribute(name, value)
}
// Access className property while respecting SVGAnimatedString
function className(node, value) {
var klass = node.className,
svg = klass && klass.baseVal !== undefined
if (value === undefined) return svg ? klass.baseVal : klass
svg ? (klass.baseVal = value) : (node.className = value)
}
// "true" => true
// "false" => false
// "null" => null
// "42" => 42
// "42.5" => 42.5
// JSON => parse if valid
// String => self
function deserializeValue(value) {
var num
try {
return value ? value == "true" || (value == "false" ? false : value == "null" ? null : !isNaN(num = Number(value)) ? num : /^[\[\{]/.test(value) ? $.parseJSON(value) : value) : value
} catch (e) {
return value
}
}
$.type = type
$.isFunction = isFunction
$.isWindow = isWindow
$.isArray = isArray
$.isPlainObject = isPlainObject
// Empty object
$.isEmptyObject = function(obj) {
var name
for (name in obj) return false
return true
}
// Get the position of the specified value in the array
$.inArray = function(elem, array, i) {
return emptyArray.indexOf.call(array, elem, i)
}
// Convert string to camelCase format
$.camelCase = camelize
// Trim whitespace from both ends of a string
$.trim = function(str) {
return str.trim()
}
// plugin compatibility
$.uuid = 0
$.support = {}
$.expr = {}
// Iterate over elements, process each record in the callback, and save the results where the processing function return value is not null or undefined
// Note that a unified for in is not used here to avoid traversing default properties of data, such as toString, valueOf for arrays
$.map = function(elements, callback) {
var value, values = [],
i, key
// If the data being traversed is an array or nodeList
if (likeArray(elements)) for (i = 0; i < elements.length; i++) {
value = callback(elements[i], i)
if (value != null) values.push(value)
} else
// If it is an object
for (key in elements) {
value = callback(elements[key], key)
if (value != null) values.push(value)
}
return values
}
value = callback(elements[key], key);
if (value != null) values.push(value);
}
return flatten(values);
}
// Traverse the array, use each data item as the context for the callback, and pass the data along with its index for processing. If the processing result of any item explicitly returns false,
// stop traversal and return elements.
$.each = function(elements, callback) {
var i, key;
if (likeArray(elements)) {
for (i = 0; i < elements.length; i++)
if (callback.call(elements[i], i, elements[i]) === false) return elements;
} else {
for (key in elements)
if (callback.call(elements[key], key, elements[key]) === false) return elements;
}
return elements;
}
// Filter
$.grep = function(elements, callback) {
return filter.call(elements, callback);
}
if (window.JSON) $.parseJSON = JSON.parse;
// Populate the class2type map
// Fill the values for class2type
$.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
class2type["[object " + name + "]"] = name.toLowerCase();
});
// Operations for DOM
// Define methods that will be available on all
// Zepto collections
$.fn = {
// Because a collection acts like an array
// copy over these useful array functions.
forEach: emptyArray.forEach,
reduce: emptyArray.reduce,
push: emptyArray.push,
sort: emptyArray.sort,
indexOf: emptyArray.indexOf,
concat: emptyArray.concat,
// `map` and `slice` in the jQuery API work differently
// from their array counterparts
map: function(fn) {
return $($.map(this, function(el, i) {
return fn.call(el, i, el);
}));
},
slice: function() {
return $(slice.apply(this, arguments));
},
// DOM Ready
ready: function(callback) {
if (readyRE.test(document.readyState)) callback($);
else document.addEventListener('DOMContentLoaded', function() {
callback($);
}, false);
return this;
},
// Get the value from the collection at the specified index. If idx is negative, idx equals idx + length, where length is the collection's length.
get: function(idx) {
return idx === undefined ? slice.call(this) : this[idx >= 0 ? idx : idx + this.length];
},
// Convert the collection to an array
toArray: function() {
return this.get();
},
// Get the size of the collection
size: function() {
return this.length;
},
// Remove the collection from the DOM
remove: function() {
return this.each(function() {
if (this.parentNode != null) this.parentNode.removeChild(this);
});
},
// Traverse the collection, process each item in the callback, and remove items where the callback explicitly returns false. Note that if the callback explicitly returns false,
// the loop will stop.
each: function(callback) {
emptyArray.every.call(this, function(el, idx) {
return callback.call(el, idx, el) !== false;
});
return this;
},
// Filter the collection, returning records where the processing result is true
filter: function(selector) {
// this.not(selector) gets the collection to be excluded, and the second negation (where this.not's parameter is a collection) gets the desired collection
if (isFunction(selector)) return this.not(this.not(selector));
// filter collects records where the return result is true
return $(filter.call(this, function(element) {
return zepto.matches(element, selector); // Collect when element matches selector
}));
},
// Append the results obtained from selector to the current collection
add: function(selector, context) {
return $(uniq(this.concat($(selector, context)))); // Append and remove duplicates
},
// Return whether the first record in the collection matches the selector
is: function(selector) {
return this.length > 0 && zepto.matches(this[0], selector);
},
// Exclude records in the collection that meet the conditions, accepting parameters: css selector, function, dom, nodeList
not: function(selector) {
var nodes = [];
// When selector is a function, typeof nodeList is also function in safari, so here we need to add an additional check for selector.call !== undefined
if (isFunction(selector) && selector.call !== undefined) {
this.each(function(idx) {
// Note that we collect records when selector.call(this, idx) returns a false result
if (!selector.call(this, idx)) nodes.push(this);
});
} else {
// When selector is a string, filter the collection, i.e., filter out records in the collection that meet the selector
var excludes = typeof selector == 'string' ? this.filter(selector) :
// When selector is a nodeList, execute slice.call(selector), note that isFunction(selector.item) is to exclude the case where selector is an array
// When selector is a css selector, execute $(selector)
(likeArray(selector) && isFunction(selector.item)) ? slice.call(selector) : $(selector);
this.forEach(function(el) {
// Filter out records not in the excludes collection to achieve exclusion
if (excludes.indexOf(el) < 0) nodes.push(el);
});
}
return $(nodes); // Since the result obtained above is an array, it needs to be converted to a zepto object to inherit other methods and enable chaining
},
/*
Accept node and string as parameters, filter the current collection to include selector
isObject(selector) checks if the parameter is a node, because typeof node == 'object'
When the parameter is a node, just check if the current record contains the node
When the parameter is a string, query selector within the current record, if the length is 0, it is false, and filter will filter out this record, otherwise keep the record
*/
has: function(selector) {
return this.filter(function() {
return isObject(selector) ? $.contains(this, selector) : $(this).find(selector).size();
});
},
/*
Select the record at the specified index in the collection. When idx is -1, take the last record
*/
eq: function(idx) {
return idx === -1 ? this.slice(idx) : this.slice(idx, +idx + 1);
},
/*
Take the first record in the collection
*/
first: function() {
var el = this[0]; // Take the first record in the collection
// If the first data in the collection is already a zepto object, return it directly, otherwise convert it to a zepto object
// el && !isObject(el) is used to check if el is a node, because if el is a node, isObject(el) will be true
return el && !isObject(el) ? el : $(el);
},
/*
Get the last record from the collection
*/
last: function() {
var el = this[this.length - 1]; // Get the last record from the collection
// If el is a node, isObject(el) will be true, and it needs to be converted to a zepto object
return el && !isObject(el) ? el : $(el);
},
/*
Find the selector within the current collection, which can be a collection, selector, or node
*/
find: function(selector) {
var result, $this = this;
// If selector is a node or zepto collection
if (typeof selector == 'object')
// Traverse selector, filter out selectors whose parent is a record in the collection
result = $(selector).filter(function() {
var node = this;
// If $.contains(parent, node) returns true, emptyArray.some will also return true, and the outer filter will include this record
return emptyArray.some.call($this, function(parent) {
return $.contains(parent, node);
});
});
// If selector is a CSS selector
// If the current collection length is 1, call zepto.qsa and convert the result to a zepto object
else if (this.length == 1) result = $(zepto.qsa(this[0], selector));
// If the length is greater than 1, use map to traverse
else result = this.map(function() {
return zepto.qsa(this, selector);
});
return result;
},
// Get the nearest parent element of the first record in the collection that meets the conditions
closest: function(selector, context) {
var node = this[0],
collection = false;
if (typeof selector == 'object') collection = $(selector);
// When selector is a node or zepto collection, if node is not in the collection, check node.parentNode
// When selector is a string selector, if node does not match selector, check node.parentNode
while (node && !(collection ? collection.indexOf(node) >= 0 : zepto.matches(node, selector)))
// When node is not context or document, get node.parentNode
node = node !== context && !isDocument(node) && node.parentNode;
return $(node);
},
// Get all parent elements of the collection
parents: function(selector) {
var ancestors = [],
nodes = this;
// Traverse nodes to get all parents, note that nodes is reassigned in the while loop
// The clever part of this function is that it keeps getting parents, then traverses those parents to get their parents
// Then it removes duplicates to get the final desired result. When it reaches the topmost parent, nodes.length becomes 0
while (nodes.length > 0)
// Reassign nodes to the collected parent collection
nodes = $.map(nodes, function(node) {
// Traverse nodes, collect the first level of parents
// ancestors.indexOf(node) < 0 is used to remove duplicates
if ((node = node.parentNode) && !isDocument(node) && ancestors.indexOf(node) < 0) {
ancestors.push(node); // Collect already obtained parent elements for duplicate removal
return node;
}
});
// Above, we only got all parent elements, here we need to filter them to get the final desired result
return filtered(ancestors, selector);
},
// Get the parent node of the collection
parent: function(selector) {
return filtered(uniq(this.pluck('parentNode')), selector);
},
children: function(selector) {
return filtered(this.map(function() {
return children(this);
}), selector);
},
contents: function() {
return this.map(function() {
return slice.call(this.childNodes);
});
},
siblings: function(selector) {
return filtered(this.map(function(i, el) {
// First get all child nodes of the parent node of this element, then exclude itself
return filter.call(children(el.parentNode), function(child) {
return child !== el;
});
}), selector);
},
empty: function() {
return this.each(function() {
this.innerHTML = '';
});
},
// Get the related collection of the current collection based on the property
pluck: function(property) {
return $.map(this, function(el) {
return el[property];
});
},
show: function() {
return this.each(function() {
// Clear the inline display="none" style of the element
this.style.display == "none" && (this.style.display = null);
// When the element's display style in the stylesheet is none, set its display to the default value
if (getComputedStyle(this, '').getPropertyValue("display") == "none") this.style.display = defaultDisplay(this.nodeName); // defaultDisplay is a method to get the default display of an element
});
},
replaceWith: function(newContent) {
// Insert the content to be replaced before the content being replaced, then remove the content being replaced
return this.before(newContent).remove();
},
wrap: function(structure) {
var func = isFunction(structure);
if (this[0] && !func)
// If structure is a string, convert it to a DOM
var dom = $(structure).get(0),
// If structure is a node already on the page or if there is more than one record to wrap, clone dom
clone = dom.parentNode || this.length > 1;
return this.each(function(index) {
$(this).wrapAll(
func ? structure.call(this, index) : clone ? dom.cloneNode(true) : dom);
});
},
wrapAll: function(structure) {
if (this[0]) {
// Insert the content to be wrapped before the first record, which is used to position structure
$(this[0]).before(structure = $(structure));
var children;
// Drill down to the innermost element
// Get the innermost element of the first child node in structure
while ((children = structure.children()).length) structure = children.first();
// Insert the current collection into the innermost node to achieve wrapAll
$(structure).append(this);
}
return this;
},
// Wrap a structure around the content inside the matched elements
wrapInner: function(structure) {
var func = isFunction(structure);
return this.each(function(index) {
// The principle is to get the content of the node, wrap the content with structure, and if the content does not exist, append structure directly to the node
var self = $(this),
contents = self.contents(),
dom = func ? structure.call(this, index) : structure;
contents.length ? contents.wrapAll(dom) : self.append(dom);
});
},
unwrap: function() {
// Replace the parent with the child elements
this.parent().each(function() {
$(this).replaceWith($(this).children());
});
return this;
};
return this;
},
// Clone node
clone: function() {
return this.map(function() {
return this.cloneNode(true);
});
},
// Hide collection
hide: function() {
return this.css("display", "none");
},
toggle: function(setting) {
return this.each(function() {
var el = $(this);
/*
The purpose of this setting is to control visibility without toggling. When its value is true, it remains visible; when false, it remains hidden.
The logic here may seem convoluted, but it's straightforward. If no toggle parameter is provided, it decides whether to show or hide the element based on whether its display is 'none'.
If a toggle parameter is provided, there's no toggling effect; it simply decides visibility based on the parameter value. If the parameter is true, it's equivalent to the show method; if false, it's equivalent to the hide method.
*/
(setting === undefined ? el.css("display") == "none" : setting) ? el.show() : el.hide();
});
},
prev: function(selector) {
return $(this.pluck('previousElementSibling')).filter(selector || '*');
},
next: function(selector) {
return $(this.pluck('nextElementSibling')).filter(selector || '*');
},
// When a parameter is provided, set the HTML for each record in the collection. Without a parameter, get the HTML of the first record in the collection. If the collection length is 0, return null.
html: function(html) {
return html === undefined ?
// When the html parameter is not provided, get the HTML of the first record in the collection
(this.length > 0 ? this[0].innerHTML : null) :
// Otherwise, iterate over the collection and set the HTML for each record
this.each(function(idx) {
// Record the original innerHTML
var originHtml = this.innerHTML;
// If the html parameter is a string, insert it directly into the record.
// If it's a function, call the function with the current record as the context, and pass the record's index and original innerHTML as parameters.
$(this).empty().append(funcArg(this, html, idx, originHtml));
});
},
text: function(text) {
// If no text parameter is provided, get the textContent of the first record if the collection length is greater than 0, otherwise return null.
// If a text parameter is provided, set the textContent of each record in the collection to the provided text.
return text === undefined ? (this.length > 0 ? this[0].textContent : null) : this.each(function() {
this.textContent = text;
});
},
attr: function(name, value) {
var result;
// When only the name is provided and it's a string, get the attribute of the first record.
return (typeof name == 'string' && value === undefined) ?
// If the collection is empty or the element is not a node, return undefined.
(this.length == 0 || this[0].nodeType !== 1 ? undefined :
// If getting the value of an input
(name == 'value' && this[0].nodeName == 'INPUT') ? this.val() :
// Note that attributes directly defined on nodes cannot be retrieved using getAttribute in standard browsers and IE9,10, resulting in null.
// For example, div.aa = 10, using div.getAttribute('aa') returns null, and it needs to be retrieved using div.aa or div['aa'].
(!(result = this[0].getAttribute(name)) && name in this[0]) ? this[0][name] : result) :
this.each(function(idx) {
if (this.nodeType !== 1) return;
// If name is an object, such as {'id':'test','value':11}, set the attributes for the data.
if (isObject(name)) for (key in name) setAttribute(this, key, name[key]);
// If name is a plain attribute string, use funcArg to handle whether value is a direct value or a function, ultimately returning an attribute value.
// If funcArg returns undefined or null, it effectively removes the element's attribute.
else setAttribute(this, name, funcArg(this, value, idx, this.getAttribute(name)));
});
},
removeAttr: function(name) {
return this.each(function() {
this.nodeType === 1 && setAttribute(this, name); // When the third parameter of setAttribute is null, it removes the name attribute.
});
},
// Get the specified name attribute of the first record or add custom attributes to each record. Note the difference from setAttribute.
prop: function(name, value) {
// When no value is provided, get the attribute. When a value is provided, add it to each record. The value can be a direct value or a function returning a value.
return (value === undefined) ? (this[0] && this[0][name]) : this.each(function(idx) {
this[name] = funcArg(this, value, idx, this[name]);
});
},
data: function(name, value) {
// Use the attr method to achieve get and set effects. Note that in the attr method, when a value exists, it returns the collection itself; if not, it returns the retrieved value.
var data = this.attr('data-' + dasherize(name), value);
return data !== null ? deserializeValue(data) : undefined;
},
val: function(value) {
return (value === undefined) ?
// If it's a multi-select select element, return an array of the values of the selected options.
(this[0] && (this[0].multiple ? $(this[0]).find('option').filter(function(o) {
return this.selected;
}).pluck('value') : this[0].value)) : this.each(function(idx) {
this.value = funcArg(this, value, idx, this.value);
});
},
offset: function(coordinates) {
if (coordinates) return this.each(function(index) {
var $this = $(this),
// When coordinates is an object, return directly; when it's a function, return the result of the function applied to coords.
coords = funcArg(this, coordinates, index, $this.offset()),
// Get the parent's offset
parentOffset = $this.offsetParent().offset(),
// Calculate the difference to determine the offset
props = {
top: coords.top - parentOffset.top,
left: coords.left - parentOffset.left
};
// Note that setting top and left has no effect if the element's position is static.
if ($this.css('position') == 'static') props['position'] = 'relative';
$this.css(props);
});
// Get the offset of the first record, including offsetTop, offsetLeft, offsetWidth, offsetHeight.
if (this.length == 0) return null;
var obj = this[0].getBoundingClientRect();
// window.pageYOffset is similar to Math.max(document.documentElement.scrollTop || document.body.scrollTop).
return {
left: obj.left + window.pageXOffset,
top: obj.top + window.pageYOffset,
width: Math.round(obj.width),
height: Math.round(obj.height)
};
},
css: function(property, value) {
// Get the specified style
if (arguments.length < 2 && typeof property == 'string') return this[0] && (this[0].style[camelize(property)] || getComputedStyle(this[0], '').getPropertyValue(property));
// Set styles
var css = '';
if (type(property) == 'string') {
if (!value && value !== 0) // Remove the property style when value is falsy (null, undefined)
this.each(function() {
// style.removeProperty removes the specified CSS property name (IE does not support the DOM style method)
this.style.removeProperty(dasherize(property));
});
else css = dasherize(property) + ":" + maybeAddPx(property, value);
} else {
// When property is an object
for (key in property)
if (!property[key] && property[key] !== 0)
// Remove the key style when property[key] is falsy
this.each(function() {
this.style.removeProperty(dasherize(key));
});
else css += dasherize(key) + ':' + maybeAddPx(key, property[key]) + ';';
}
// Apply styles
return this.each(function() {
this.style.cssText += ';' + css;
});
},
index: function(element) {
// $(element)[0] converts the string to a node, as this is an array containing nodes
// When element is not specified, get the position of the first record in its parent node
// this.parent().children().indexOf(this[0]) is巧妙, equivalent to parent().children().indexOf(this) for the first record
return element ? this.indexOf($(element)[0]) : this.parent().children().indexOf(this[0]);
},
hasClass: function(name) {
return emptyArray.some.call(this, function(el) {
// Note that this refers to the regex generated by classRE(name)
return this.test(className(el));
}, classRE(name));
},
addClass: function(name) {
return this.each(function(idx) {
classList = [];
var cls = className(this),
newName = funcArg(this, name, idx, cls);
// Handle multiple classes separated by spaces
newName.split(/\s+/g).forEach(function(klass) {
if (!$(this).hasClass(klass)) classList.push(klass);
}, this);
classList.length && className(this, cls + (cls ? " " : "") + classList.join(" "));
});
},
removeClass: function(name) {
return this.each(function(idx) {
if (name === undefined) return className(this, '');
classList = className(this);
funcArg(this, name, idx, classList).split(/\s+/g).forEach(function(klass) {
classList = classList.replace(classRE(klass), " ");
});
className(this, classList.trim());
});
},
toggleClass: function(name, when) {
return this.each(function(idx) {
var $this = $(this),
names = funcArg(this, name, idx, className(this));
names.split(/\s+/g).forEach(function(klass) {
(when === undefined ? !$this.hasClass(klass) : when) ? $this.addClass(klass) : $this.removeClass(klass);
});
});
},
scrollTop: function() {
if (!this.length) return;
return ('scrollTop' in this[0]) ? this[0].scrollTop : this[0].scrollY;
},
position: function() {
if (!this.length) return;
var elem = this[0],
// Get the real offsetParent
offsetParent = this.offsetParent(),
// Get correct offsets
offset = this.offset(),
parentOffset = rootNodeRE.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset();
// Subtract element margins
// note: when an element has margin: auto, offsetLeft and marginLeft
// are the same in Safari causing offset.left to incorrectly be 0
offset.top -= parseFloat($(elem).css('margin-top')) || 0;
offset.left -= parseFloat($(elem).css('margin-left')) || 0;
// Add offsetParent borders
parentOffset.top += parseFloat($(offsetParent[0]).css('border-top-width')) || 0;
parentOffset.left += parseFloat($(offsetParent[0]).css('border-left-width')) || 0;
// Subtract the two offsets
return {
top: offset.top - parentOffset.top,
left: offset.left - parentOffset.left
};
},
offsetParent: function() {
return this.map(function() {
var parent = this.offsetParent || document.body;
while (parent && !rootNodeRE.test(parent.nodeName) && $(parent).css("position") == "static")
parent = parent.offsetParent;
return parent;
});
}
// For now
$.fn.detach = $.fn.remove;
// Generate the `width` and `height` functions
['width', 'height'].forEach(function(dimension) {
$.fn[dimension] = function(value) {
var offset, el = this[0],
// Convert width, height to Width, Height for window or document dimensions
Dimension = dimension.replace(/./, function(m) { return m[0].toUpperCase(); });
// No parameter for getting, use innerWidth, innerHeight for window
if (value === undefined) return isWindow(el) ? el['inner' + Dimension] :
// Use offsetWidth, offsetHeight for document
isDocument(el) ? el.documentElement['offset' + Dimension] : (offset = this.offset()) && offset[dimension];
else return this.each(function(idx) {
el = $(this);
el.css(dimension, funcArg(this, value, idx, el[dimension]()));
});
};
});
function traverseNode(node, fun) {
fun(node);
for (var key in node.childNodes) traverseNode(node.childNodes[key], fun);
}
// Generate the `after`, `prepend`, `before`, `append`,
// `insertAfter`, `insertBefore`, `appendTo`, and `prependTo` methods.
adjacencyOperators.forEach(function(operator, operatorIndex) {
var inside = operatorIndex % 2; //=> prepend, append
$.fn[operator] = function() {
// arguments can be nodes, arrays of nodes, Zepto objects and HTML strings
var argType, nodes = $.map(arguments, function(arg) {
argType = type(arg);
return argType == "object" || argType == "array" || arg == null ? arg : zepto.fragment(arg);
}),
parent, copyByClone = this.length > 1; // if the collection length is greater than one, the nodes to be inserted need to be cloned
if (nodes.length < 1) return this;
return this.each(function(_, target) {
parent = inside ? target : target.parentNode;
// Convert after, prepend, append operations to before operation by changing target, insertBefore's second parameter being null is equivalent to appendChild operation
target = operatorIndex == 0 ? target.nextSibling : operatorIndex == 1 ? target.firstChild : operatorIndex == 2 ? target : null;
nodes.forEach(function(node) {
if (copyByClone) node = node.cloneNode(true);
else if (!parent) return $(node).remove();
// After inserting the node, if the inserted node is a SCRIPT, execute its content with window as the context
traverseNode(parent.insertBefore(node, target), function(el) {
if (el.nodeName != null && el.nodeName.toUpperCase() === 'SCRIPT' && (!el.type || el.type === 'text/javascript') && !el.src) window['eval'].call(window, el.innerHTML);
});
});
});
};
// after => insertAfter
// prepend => prependTo
// before => insertBefore
// append => appendTo
$.fn[inside ? operator + 'To' : 'insert' + (operatorIndex ? 'Before' : 'After')] = function(html) {
$(html)[operator](this);
return this;
};
});
zepto.Z.prototype = $.fn;
// Export internal API functions in the `$.zepto` namespace
zepto.uniq = uniq;
zepto.deserializeValue = deserializeValue;
$.zepto = zepto;
return $;
})();
window.Zepto = Zepto;
'$' in window || (window.$ = Zepto);
;(function($) {
function detect(ua) {
var os = this.os = {}, browser = this.browser = {},
webkit = ua.match(/WebKit\/([\d.]+)/),
android = ua.match(/(Android)\s+([\d.]+)/),
ipad = ua.match(/(iPad).*OS\s([\d_]+)/),
iphone = !ipad && ua.match(/(iPhone\sOS)\s([\d_]+)/),
webos = ua.match(/(webOS|hpwOS)[\s\/]([\d.]+)/),
touchpad = webos && ua.match(/TouchPad/),
kindle = ua.match(/Kindle\/([\d.]+)/),
silk = ua.match(/Silk\/([\d._]+)/),
blackberry = ua.match(/(BlackBerry).*Version\/([\d.]+)/),
bb10 = ua.match(/(BB10).*Version\/([\d.]+)/),
rimtabletos = ua.match(/(RIM\sTablet\sOS)\s([\d.]+)/),
playbook = ua.match(/PlayBook/),
chrome = ua.match(/Chrome\/([\d.]+)/) || ua.match(/CriOS\/([\d.]+)/),
firefox = ua.match(/Firefox\/([\d.]+)/)
if (browser.webkit = !!webkit) browser.version = webkit[1];
if (android) os.android = true, os.version = android[2];
if (iphone) os.ios = os.iphone = true, os.version = iphone[2].replace(/_/g, '.');
if (ipad) os.ios = os.ipad = true, os.version = ipad[2].replace(/_/g, '.');
if (webos) os.webos = true, os.version = webos[2];
if (touchpad) os.touchpad = true;
if (blackberry) os.blackberry = true, os.version = blackberry[2];
if (bb10) os.bb10 = true, os.version = bb10[2];
if (rimtabletos) os.rimtabletos = true, os.version = rimtabletos[2];
if (playbook) browser.playbook = true;
if (kindle) os.kindle = true, os.version = kindle[1];
if (silk) browser.silk = true, browser.version = silk[1];
if (!silk && os.android && ua.match(/Kindle Fire/)) browser.silk = true;
if (chrome) browser.chrome = true, browser.version = chrome[1];
if (firefox) browser.firefox = true, browser.version = firefox[1];
os.tablet = !!(ipad || playbook || (android && !ua.match(/Mobile/)) || (firefox && ua.match(/Tablet/)));
os.phone = !!(!os.tablet && (android || iphone || webos || blackberry || bb10 || (chrome && ua.match(/Android/)) || (chrome && ua.match(/CriOS\/([\d.]+)/)) || (firefox && ua.match(/Mobile/))));
}
detect.call($, navigator.userAgent);
// make available to unit tests
$.__detect = detect;
})(Zepto);
/*
Event handling section
*/
;
(function($) {
var $$ = $.zepto.qsa,
handlers = {}, _zid = 1,
specialEvents = {},
hover = {
mouseenter: 'mouseover',
mouseleave: 'mouseout'
};
specialEvents.click = specialEvents.mousedown = specialEvents.mouseup = specialEvents.mousemove = 'MouseEvents';
// Get the unique identifier for the element, if not set, assign one and return it
function zid(element) {
return element._zid || (element._zid = _zid++);
}
// Find the event handler collection bound to the element for the specified event type
function findHandlers(element, event, fn, selector) {
event = parse(event);
if (event.ns) var matcher = matcherFor(event.ns);
return (handlers[zid(element)] || []).filter(function(handler) {
return handler && (!event.e || handler.e == event.e) // Check if event type is the same
&&
(!event.ns || matcher.test(handler.ns)) // Check if event namespace is the same
// Note that functions are reference types. zid(handler.fn) returns the identifier of handler.fn, and if it doesn't have one, it assigns one.
// This way, if fn and handler.fn reference the same function, fn should also have the same identifier.
// Here, we use this to determine if two variables reference the same function.
&&
(!fn || zid(handler.fn) === zid(fn)) && (!selector || handler.sel == selector);
});
}
// Parse event type, returning an object containing the event name and event namespace
function parse(event) {
var parts = ('' + event).split('.');
return {
e: parts[0],
ns: parts.slice(1).sort().join(' ')
};
}
// Generate namespace regex
function matcherFor(ns) {
return new RegExp('(?:^| )' + ns.replace(' ', ' .* ?') + '(?: |$)');
}
// Iterate over events
function eachEvent(events, fn, iterator) {
if ($.type(events) != "string") $.each(events, iterator);
else events.split(/\s/).forEach(function(type) {
iterator(type, fn);
});
}
// Set focus and blur events to capture to achieve event bubbling
function eventCapture(handler, captureSetting) {
return handler.del && (handler.e == 'focus' || handler.e == 'blur') || !!captureSetting;
}
// Fix for unsupported mouseenter and mouseleave events
function realEvent(type) {
return hover[type] || type;
}
// Bind event listeners to elements, can bind multiple event types, e.g., ['click','mouseover','mouseout'], or 'click mouseover mouseout'
function add(element, events, fn, selector, getDelegate, capture) {
var id = zid(element),
set = (handlers[id] || (handlers[id] = [])); // All event handlers already bound to the element
eachEvent(events, fn, function(event, fn) {
var handler = parse(event);
// Save fn, modified below for handling mouseenter, mouseleave
handler.fn = fn;
handler.sel = selector;
// Mimic mouseenter, mouseleave
if (handler.e in hover) fn = function(e) {
/*
relatedTarget is the event-related object, only has a value during mouseover and mouseout events
During mouseover, it represents the object the mouse moved out from, during mouseout, it represents the object the mouse moved into
When related doesn't exist, the event is not mouseover or mouseout. During mouseover, !$.contains(this, related) means the related object is not within the event object
and related !== this means the related object is not the event object itself, indicating the mouse has moved from outside the event object to the object itself, which is when the handler should execute
When the mouse moves from the event object into a child node, related equals this, and !$.contains(this, related) is not true, so the handler should not execute
*/
var related = e.relatedTarget;
if (!related || (related !== this && !$.contains(this, related))) return handler.fn.apply(this, arguments);
};
// Event delegation
handler.del = getDelegate && getDelegate(fn, event);
var callback = handler.del || fn;
handler.proxy = function(e) {
var result = callback.apply(element, [e].concat(e.data));
// When the event handler returns false, prevent default action and stop propagation
if (result === false) e.preventDefault(), e.stopPropagation();
return result;
};
// Set the handler's position in the function set
handler.i = set.length;
// Store the function in the function set
set.push(handler);
element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture));
});
}
// Remove specified event listeners from elements, can remove multiple event types specified functions, using arrays or space-separated strings, same as add
function remove(element, events, fn, selector, capture) {
var id = zid(element);
eachEvent(events || '', fn, function(event, fn) {
findHandlers(element, event, fn, selector).forEach(function(handler) {
delete handlers[id][handler.i];
element.removeEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture));
});
});
}
$.event = {
add: add,
remove: remove
};
// Set proxy
$.proxy = function(fn, context) {
if ($.isFunction(fn)) {
// If fn is a function, declare a new function and call fn with context as the context
var proxyFn = function() {
return fn.apply(context, arguments);
};
// Reference fn identifier
proxyFn._zid = zid(fn);
return proxyFn;
} else if (typeof context == 'string') {
return $.proxy(fn[context], fn);
} else {
throw new TypeError("expected function");
}
};
$.fn.bind = function(event, callback) {
return this.each(function() {
add(this, event, callback);
});
};
$.fn.unbind = function(event, callback) {
return this.each(function() {
remove(this, event, callback);
});
};
// Bind a one-time event listener
$.fn.one = function(event, callback) {
return this.each(function(i, element) {
// Add function, then delete the binding in the callback function to achieve a one-time event
add(this, event, callback, null, function(fn, type) {
return function() {
var result = fn.apply(element, arguments); // Execute the bound callback here
remove(element, type, fn); // Delete the binding above
return result;
};
});
});
};
var returnTrue = function() {
return true;
},
returnFalse = function() {
return false;
},
ignoreProperties = /^([A-Z]|layer[XY]$)/,
eventMethods = {
preventDefault: 'isDefaultPrevented', // Whether preventDefault method has been called
// Cancel execution of other event handlers and stop event propagation. If multiple event handlers are bound to the same event, calling this method in one handler will prevent other handlers from executing.
stopImmediatePropagation: 'isImmediatePropagationStopped', // Whether stopImmediatePropagation method has been called
};
stopPropagation: 'isPropagationStopped' // Whether the stopPropagation method has been called } // Create event proxy
function createProxy(event) { var key, proxy = { originalEvent: event } // Save the original event for (key in event) if (!ignoreProperties.test(key) && event[key] !== undefined) proxy[key] = event[key] // Copy event properties to proxy
// Define preventDefault, stopImmediatePropagation, stopPropagation methods on proxy $.each(eventMethods, function(name, predicate) { proxy[name] = function() { this[predicate] = returnTrue return event[name].apply(event, arguments) } proxy[predicate] = returnFalse }) return proxy }
// Emulates the 'defaultPrevented' property for browsers that do not support it // event.defaultPrevented returns a boolean indicating whether the event's default action was prevented, i.e., whether the event.preventDefault() method was called.
function fix(event) { if (!('defaultPrevented' in event)) { event.defaultPrevented = false // Initial value false var prevent = event.preventDefault // Reference the default preventDefault event.preventDefault = function() { // Override preventDefault this.defaultPrevented = true prevent.call(this) } } } // Event delegation $.fn.delegate = function(selector, event, callback) { return this.each(function(i, element) { add(element, event, callback, selector, function(fn) { return function(e) { // If the event target is an element within element, find the one matching selector var evt, match = $(e.target).closest(selector, element).get(0) if (match) { // evt becomes an object with preventDefault, stopImmediatePropagation, stopPropagation methods, currentTarget, liveFired properties, and also default properties of e evt = $.extend(createProxy(e), { currentTarget: match, liveFired: element }) return fn.apply(match, [evt].concat([].slice.call(arguments, 1))) } } }) }) } // Remove event delegation $.fn.undelegate = function(selector, event, callback) { return this.each(function() { remove(this, event, callback, selector) }) }
$.fn.live = function(event, callback) { $(document.body).delegate(this.selector, event, callback) return this } $.fn.die = function(event, callback) { $(document.body).undelegate(this.selector, event, callback) return this }
// on also has live and event delegation effects, so you can use on to bind events $.fn.on = function(event, selector, callback) { return !selector || $.isFunction(selector) ? this.bind(event, selector || callback) : this.delegate(selector, event, callback) } $.fn.off = function(event, selector, callback) { return !selector || $.isFunction(selector) ? this.unbind(event, selector || callback) : this.undelegate(selector, event, callback) } // Trigger event manually $.fn.trigger = function(event, data) { if (typeof event == 'string' || $.isPlainObject(event)) event = $.Event(event) fix(event) event.data = data return this.each(function() { // Items in the collection might not be DOM elements // (todo: possibly support events on plain old objects) if ('dispatchEvent' in this) this.dispatchEvent(event) }) }
// Triggers event handlers on the current element as if an event occurred, // does not trigger an actual event, does not bubble // Trigger specified type of event on the element, but does not bubble $.fn.triggerHandler = function(event, data) { var e, result this.each(function(i, element) { e = createProxy(typeof event == 'string' ? $.Event(event) : event) e.data = data e.target = element // Iterate over the specified type of event handler functions on the element, execute them in order, if stopImmediatePropagation is executed, // e.isImmediatePropagationStopped() will return true, and the outer function will return false // Note that the callback function in each specifies that returning false will break the loop, thus stopping the execution of the callback function $.each(findHandlers(element, event.type || event), function(i, handler) { result = handler.proxy(e) if (e.isImmediatePropagationStopped()) return false }) }) return result }
// Shortcut methods for .bind(event, fn)
for each event type
;
('focusin focusout load resize scroll unload click dblclick ' +
'mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave ' +
'change select keydown keypress keyup error').split(' ').forEach(function(event) {
$.fn[event] = function(callback) {
return callback ?
// If callback is provided, it is assumed to be binding
this.bind(event, callback) :
// If no callback is provided, it triggers the event
this.trigger(event)
}
})
; ['focus', 'blur'].forEach(function(name) { $.fn[name] = function(callback) { if (callback) this.bind(name, callback) else this.each(function() { try { thisname } catch (e) {} }) return this } })
// Create an event object based on parameters $.Event = function(type, props) { // When type is an object if (typeof type != 'string') props = type, type = props.type // Create an event object, for click, mouseover, mouseout, create MouseEvent, bubbles indicates whether to bubble var event = document.createEvent(specialEvents[type] || 'Events'), bubbles = true // Ensure bubbles is true or false, and extend properties from props to the newly created event object if (props) for (var name in props) (name == 'bubbles') ? (bubbles = !!props[name]) : (event[name] = props[name]) event.initEvent(type, bubbles, true, null) return event }
if (props) for (var name in props) (name == 'bubbles') ? (bubbles = !!props[name]) : (event[name] = props[name]);
// Initialize event object, type is the event type such as click, bubbles indicates whether it bubbles, the third parameter indicates whether the default action can be canceled using preventDefault method
event.initEvent(type, bubbles, true, null, null, null, null, null, null, null, null, null, null, null, null);
// Add isDefaultPrevented method, event.defaultPrevented returns a boolean value indicating whether the default action of the current event has been canceled, which means whether the event.preventDefault() method has been executed.
event.isDefaultPrevented = function() {
return this.defaultPrevented;
};
return event;
})(Zepto);
/**
Ajax handling part
**/
;(function($) {
var jsonpID = 0,
document = window.document,
key,
name,
rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
scriptTypeRE = /^(?:text|application)\/javascript/i,
xmlTypeRE = /^(?:text|application)\/xml/i,
jsonType = 'application/json',
htmlType = 'text/html',
blankRE = /^\s*$/;
// Trigger a custom event and return false if it was canceled
function triggerAndReturn(context, eventName, data) {
var event = $.Event(eventName);
$(context).trigger(event, data);
return !event.defaultPrevented;
}
// Trigger an Ajax "global" event
function triggerGlobal(settings, context, eventName, data) {
if (settings.global) return triggerAndReturn(context || document, eventName, data);
}
// Number of active Ajax requests
$.active = 0;
// settings.global is true if global ajax events are to be triggered
// Note the $.active++ === 0 is巧妙, used to determine the start, because $.active is only equal to 0 when $.active++ === 0 is true
function ajaxStart(settings) {
if (settings.global && $.active++ === 0) triggerGlobal(settings, null, 'ajaxStart');
}
// Note the !(--$.active) similarly, --$.active is 0, which means $.active was 1, this is used to determine the end, which is interesting
function ajaxStop(settings) {
if (settings.global && !(--$.active)) triggerGlobal(settings, null, 'ajaxStop');
}
// Triggers an extra global event "ajaxBeforeSend" that's like "ajaxSend" but cancelable
// Triggers the global ajaxBeforeSend event, if it returns false, cancels the request
function ajaxBeforeSend(xhr, settings) {
var context = settings.context;
if (settings.beforeSend.call(context, xhr, settings) === false || triggerGlobal(settings, context, 'ajaxBeforeSend', [xhr, settings]) === false) return false;
triggerGlobal(settings, context, 'ajaxSend', [xhr, settings]);
}
function ajaxSuccess(data, xhr, settings) {
var context = settings.context,
status = 'success';
settings.success.call(context, data, status, xhr);
triggerGlobal(settings, context, 'ajaxSuccess', [xhr, settings, data]);
ajaxComplete(status, xhr, settings);
}
// type: "timeout", "error", "abort", "parsererror"
function ajaxError(error, type, xhr, settings) {
var context = settings.context;
settings.error.call(context, xhr, type, error);
triggerGlobal(settings, context, 'ajaxError', [xhr, settings, error]);
ajaxComplete(type, xhr, settings);
}
// status: "success", "notmodified", "error", "timeout", "abort", "parsererror"
function ajaxComplete(status, xhr, settings) {
var context = settings.context;
settings.complete.call(context, xhr, status);
triggerGlobal(settings, context, 'ajaxComplete', [xhr, settings]);
ajaxStop(settings);
}
// Empty function, used as default callback
function empty() {}
// Refer to http://zh.wikipedia.org/zh-cn/JSONP
$.ajaxJSONP = function(options) {
if (!('type' in options)) return $.ajax(options);
var callbackName = 'jsonp' + (++jsonpID), // Create callback function name
script = document.createElement('script'),
// JS file loaded
cleanup = function() {
clearTimeout(abortTimeout); // Clear the timeout event handler below
$(script).remove(); // Remove the created script tag, as the JS content has already been parsed
delete window[callbackName]; // Clear the specified callback function
},
// Cancel loading
abort = function(type) {
cleanup();
// In case of manual abort or timeout, keep an empty function as callback
// so that the SCRIPT tag that eventually loads won't result in an error.
// Here, by reassigning the callback function to an empty function, it seems to prevent the loading of JS. In fact, the request has already been generated once the src attribute is set for the script tag and cannot be interrupted.
if (!type || type == 'timeout') window[callbackName] = empty;
ajaxError(null, type || 'abort', xhr, options);
},
xhr = { abort: abort }, abortTimeout;
if (ajaxBeforeSend(xhr, options) === false) {
abort('abort');
return false;
}
// Callback function after successful loading
window[callbackName] = function(data) {
cleanup();
ajaxSuccess(data, xhr, options);
};
script.onerror = function() {
abort('error');
};
// Append the callback function name to the request URL and assign it to script, thus the request is generated
script.src = options.url.replace(/=\?/, '=' + callbackName);
$('head').append(script);
// If timeout handling is set
if (options.timeout > 0) abortTimeout = setTimeout(function() {
abort('timeout');
}, options.timeout);
return xhr;
};
// Global ajax settings
$.ajaxSettings = {
// Default type of request
type: 'GET',
// Callback that is executed before request
beforeSend: empty,
// Callback that is executed if the request succeeds
success: empty,
// Callback that is executed if the server returns an error
error: empty,
// Callback that is executed on request completion (both: error and success)
complete: empty,
// The context for the callbacks
context: null,
// Whether to trigger "global" Ajax events
global: true,
// Transport
xhr: function() {
return new window.XMLHttpRequest()
},
// MIME types mapping
accepts: {
script: 'text/javascript, application/javascript',
json: jsonType,
xml: 'application/xml, text/xml',
html: htmlType,
text: 'text/plain'
},
// Whether the request is to another domain
crossDomain: false,
// Default timeout
timeout: 0,
// Whether data should be serialized to string
processData: true,
// Whether the browser should be allowed to cache GET responses
cache: true
};
// Returns the corresponding data type based on MIME, used for the dataType in ajax parameters, setting the expected return data type
// Such as html, json, script, xml, text
function mimeToDataType(mime) {
if (mime) mime = mime.split(';', 2)[0]
return mime && (mime == htmlType ? 'html' : mime == jsonType ? 'json' : scriptTypeRE.test(mime) ? 'script' : xmlTypeRE.test(mime) && 'xml') || 'text'
}
// Appends the query string to the URL
function appendQuery(url, query) {
// Note the replace, it replaces the first matched & or &&, &?, ? ?& ?? with ?, to ensure the correctness of the address
return (url + '&' + query).replace(/[&?]{1,2}/, '?')
}
// Serialize payload and append it to the URL for GET requests
// Serializes the data sent to the server, if it's a GET request, appends the serialized data to the request URL
function serializeData(options) {
// options.processData indicates whether to automatically convert options.data to a string for non-GET requests, provided that options.data is not a string
if (options.processData && options.data && $.type(options.data) != "string")
// options.traditional indicates whether to serialize using $.param method
options.data = $.param(options.data, options.traditional)
if (options.data && (!options.type || options.type.toUpperCase() == 'GET'))
// If it's a GET request, appends the serialized data to the request URL
options.url = appendQuery(options.url, options.data)
}
$.ajax = function(options) {
// Note that you cannot directly replace $.ajaxSettings with the first parameter of $.extend, as this would alter the values in $.ajaxSettings
// The approach here is to create a new object
var settings = $.extend({}, options || {})
// Copies $.ajaxSettings[key] only if it is not defined in settings
for (key in $.ajaxSettings) if (settings[key] === undefined) settings[key] = $.ajaxSettings[key]
// Executes global ajaxStart
ajaxStart(settings)
// Sets crossDomain by checking if the request URL and the current page URL have the same host
if (!settings.crossDomain) settings.crossDomain = /^([\w-]+:)?\/\/([^\/]+)/.test(settings.url) && RegExp.$2 != window.location.host
// If the request URL is not set, it defaults to the current page URL
if (!settings.url) settings.url = window.location.toString();
// Converts data
serializeData(settings);
// If caching is not set, appends a timestamp to the URL
if (settings.cache === false) settings.url = appendQuery(settings.url, '_=' + Date.now())
// If the request is for jsonp, replaces =? in the URL with callback=?, acting as a shorthand
var dataType = settings.dataType,
hasPlaceholder = /=\?/.test(settings.url)
if (dataType == 'jsonp' || hasPlaceholder) {
if (!hasPlaceholder) settings.url = appendQuery(settings.url, 'callback=?')
return $.ajaxJSONP(settings)
}
var mime = settings.accepts[dataType],
baseHeaders = {},
// If the request protocol is not defined, it defaults to the current page protocol
protocol = /^([\w-]+:)\/\//.test(settings.url) ? RegExp.$1 : window.location.protocol,
xhr = settings.xhr(),
abortTimeout
// If not cross-domain
if (!settings.crossDomain) baseHeaders['X-Requested-With'] = 'XMLHttpRequest'
if (mime) {
baseHeaders['Accept'] = mime
if (mime.indexOf(',') > -1) mime = mime.split(',', 2)[0]
xhr.overrideMimeType && xhr.overrideMimeType(mime)
}
// If it's not a GET request, sets the content encoding type for sending data to the server
if (settings.contentType || (settings.contentType !== false && settings.data && settings.type.toUpperCase() != 'GET')) baseHeaders['Content-Type'] = (settings.contentType || 'application/x-www-form-urlencoded')
settings.headers = $.extend(baseHeaders, settings.headers || {})
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
xhr.onreadystatechange = empty;
clearTimeout(abortTimeout)
var result, error = false
// Checks the status to determine if the request was successful
// Status >= 200 && < 300 indicates success
// Status == 304 indicates the file has not been modified and can also be considered successful
// If fetching a local file, it can also be considered successful, xhr.status == 0 occurs when the page is directly opened without using localhost
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304 || (xhr.status == 0 && protocol == 'file:')) {
// Retrieves the data type of the returned data
dataType = dataType || mimeToDataType(xhr.getResponseHeader('content-type'))
result = xhr.responseText
try {
// http://perfectionkills.com/global-eval-what-are-the-options/
if (dataType == 'script')(1, eval)(result) // If the returned data type is JS
else if (dataType == 'xml') result = xhr.responseXML
else if (dataType == 'json') result = blankRE.test(result) ? null : $.parseJSON(result)
} catch (e) {
error = e
}
// If parsing fails, executes the global parsererror event
if (error) ajaxError(error, 'parsererror', xhr, settings);
// Otherwise execute ajaxSuccess
else ajaxSuccess(result, xhr, settings);
} else {
// If the request fails, execute the corresponding error handling function based on xhr.status
ajaxError(null, xhr.status ? 'error' : 'abort', xhr, settings);
}
}
}
var async = 'async' in settings ? settings.async : true;
xhr.open(settings.type, settings.url, async);
// Set request header information
for (name in settings.headers) xhr.setRequestHeader(name, settings.headers[name]);
// If ajaxBeforeSend function returns false, cancel the request
if (ajaxBeforeSend(xhr, settings) === false) {
xhr.abort();
return false;
}
// When settings.timeout is set, cancel the request after timeout and execute the timeout event handler
if (settings.timeout > 0) abortTimeout = setTimeout(function() {
xhr.onreadystatechange = empty;
xhr.abort();
ajaxError(null, 'timeout', xhr, settings);
}, settings.timeout);
// Avoid sending empty string (#319)
xhr.send(settings.data ? settings.data : null);
return xhr;
}
// Handle optional data/success arguments
// Convert arguments to the format specified by the ajax function
function parseArguments(url, data, success, dataType) {
var hasData = !$.isFunction(data); // If data is a function, it is considered as the callback after successful request
return {
url: url,
data: hasData ? data : undefined, // If data is not a function instance
success: !hasData ? data : $.isFunction(success) ? success : undefined,
dataType: hasData ? dataType || success : success
};
}
// Simple GET request
$.get = function(url, data, success, dataType) {
return $.ajax(parseArguments.apply(null, arguments));
}
$.post = function(url, data, success, dataType) {
var options = parseArguments.apply(null, arguments);
options.type = 'POST';
return $.ajax(options);
}
$.getJSON = function(url, data, success) {
var options = parseArguments.apply(null, arguments);
options.dataType = 'json';
return $.ajax(options);
}
// The url here can be in the form of http://www.xxxx.com selector, which means filtering the loaded HTML
$.fn.load = function(url, data, success) {
if (!this.length) return this;
// Split the request URL by spaces
var self = this,
parts = url.split(/\s/),
selector,
options = parseArguments(url, data, success),
callback = options.success;
if (parts.length > 1) options.url = parts[0], selector = parts[1];
// Rewrite the success callback function because it needs to add the loaded HTML to the current collection
options.success = function(response) {
// selector is the condition for filtering the requested data, such as only getting tags with class .test from the data
self.html(selector ? $('<div>').html(response.replace(rscript, "")).find(selector) : response);
// This is your callback
callback && callback.apply(self, arguments);
}
$.ajax(options);
return this;
}
var escape = encodeURIComponent;
function serialize(params, obj, traditional, scope) {
var type, array = $.isArray(obj);
$.each(obj, function(key, value) {
type = $.type(value);
// scope is used to handle cases where value is also an object or array
// traditional indicates whether to concatenate data in the traditional way,
// traditional means that if there is a data {a:[1,2,3]}, the result after converting to query string is 'a=1&a=2&a=3'
// non-traditional result is a[]=1&a[]=2&a[]=3
if (scope) key = traditional ? scope : scope + '[' + (array ? '' : key) + ']';
// handle data in serializeArray() format
// When the data to be processed is in the format of [{},{},{}], it usually refers to the result after serializing a form
if (!scope && array) params.add(value.name, value.value);
// recurse into nested objects
// When the value is an array or an object and not serialized in the traditional way, it needs to be traversed again
else if (type == "array" || (!traditional && type == "object")) serialize(params, value, traditional, key);
else params.add(key, value);
});
}
// Convert obj to the format of query string, traditional indicates whether to convert to the traditional way, see the comment above for the meaning of the traditional way
$.param = function(obj, traditional) {
var params = [];
// Note that the add method is defined on params, so there is no need to return data below serialize
params.add = function(k, v) {
this.push(escape(k) + '=' + escape(v));
}
serialize(params, obj, traditional);
return params.join('&').replace(/%20/g, '+');
}
})(Zepto);
;(function($) {
// Serialize form, return an array like [{name:value},{name2:value2}]
$.fn.serializeArray = function() {
var result = [],
el;
// Convert all form elements in the first form of the collection to an array and iterate over it
$(Array.prototype.slice.call(this.get(0).elements)).each(function() {
el = $(this);
var type = el.attr('type');
// Check its type attribute, exclude fieldset, submit, reset, button, and unselected radio and checkbox
if (this.nodeName.toLowerCase() != 'fieldset' && !this.disabled && type != 'submit' && type != 'reset' && type != 'button' &&
// Note the writing here, when the element is neither radio nor checkbox, return true directly,
// When the element is radio or checkbox, the following this.checked will be executed, when radio or checkbox is selected, this.checked will get true value
// This way, you can filter the selected radio and checkbox
((type != 'radio' && type != 'checkbox') || this.checked)) result.push({
name: el.attr('name'),
value: el.val()
});
});
return result;
}
// Convert the form values to the format of name1=value1&name2=value2
$.fn.serialize = function() {
var result = [];
this.serializeArray().forEach(function(elm) {
result.push(encodeURIComponent(elm.name) + '=' + encodeURIComponent(elm.value));
});
return result.join('&');
}
// Form submission
$.fn.submit = function(callback) {
if (callback) this.bind('submit', callback);
else if (this.length) {
var event = $.Event('submit')
this.eq(0).trigger(event)
if (!event.defaultPrevented) this.get(0).submit()
}
return this
})(Zepto)
// CSS3 Animation
;
(function($, undefined) {
var prefix = '',
eventPrefix, endEventName, endAnimationName,
vendors = {
Webkit: 'webkit',
Moz: '',
O: 'o',
ms: 'MS'
},
document = window.document,
testEl = document.createElement('div'),
supportedTransforms = /^((translate|rotate|scale)(X|Y|Z|3d)?|matrix(3d)?|perspective|skew(X|Y)?)$/i,
transform,
transitionProperty, transitionDuration, transitionTiming,
animationName, animationDuration, animationTiming,
cssReset = {}
function dasherize(str) {
return downcase(str.replace(/([a-z])([A-Z])/, '$1-$2'))
}
function downcase(str) {
return str.toLowerCase()
}
function normalizeEvent(name) {
return eventPrefix ? eventPrefix + name : downcase(name)
}
$.each(vendors, function(vendor, event) {
if (testEl.style[vendor + 'TransitionProperty'] !== undefined) {
prefix = '-' + downcase(vendor) + '-'
eventPrefix = event
return false
}
})
transform = prefix + 'transform'
cssReset[transitionProperty = prefix + 'transition-property'] = cssReset[transitionDuration = prefix + 'transition-duration'] = cssReset[transitionTiming = prefix + 'transition-timing-function'] = cssReset[animationName = prefix + 'animation-name'] = cssReset[animationDuration = prefix + 'animation-duration'] = cssReset[animationTiming = prefix + 'animation-timing-function'] = ''
$.fx = {
off: (eventPrefix === undefined && testEl.style.transitionProperty === undefined),
speeds: {
_default: 400,
fast: 200,
slow: 600
},
cssPrefix: prefix,
transitionEnd: normalizeEvent('TransitionEnd'),
animationEnd: normalizeEvent('AnimationEnd')
}
$.fn.animate = function(properties, duration, ease, callback) {
if ($.isPlainObject(duration)) ease = duration.easing, callback = duration.complete, duration = duration.duration
if (duration) duration = (typeof duration == 'number' ? duration : ($.fx.speeds[duration] || $.fx.speeds._default)) / 1000
return this.anim(properties, duration, ease, callback)
}
$.fn.anim = function(properties, duration, ease, callback) {
var key, cssValues = {}, cssProperties, transforms = '',
that = this,
wrappedCallback, endEvent = $.fx.transitionEnd
if (duration === undefined) duration = 0.4
if ($.fx.off) duration = 0
if (typeof properties == 'string') {
cssValues[animationName] = properties
cssValues[animationDuration] = duration + 's'
cssValues[animationTiming] = (ease || 'linear')
endEvent = $.fx.animationEnd
} else {
cssProperties = []
for (key in properties)
if (supportedTransforms.test(key)) transforms += key + '(' + properties[key] + ') '
else cssValues[key] = properties[key], cssProperties.push(dasherize(key))
if (transforms) cssValues[transform] = transforms, cssProperties.push(transform)
if (duration > 0 && typeof properties === 'object') {
cssValues[transitionProperty] = cssProperties.join(', ')
cssValues[transitionDuration] = duration + 's'
cssValues[transitionTiming] = (ease || 'linear')
}
}
wrappedCallback = function(event) {
if (typeof event !== 'undefined') {
if (event.target !== event.currentTarget) return
$(event.target).unbind(endEvent, wrappedCallback)
}
$(this).css(cssReset)
callback && callback.call(this)
}
if (duration > 0) this.bind(endEvent, wrappedCallback)
this.size() && this.get(0).clientLeft
this.css(cssValues)
if (duration <= 0) setTimeout(function() {
that.each(function() {
wrappedCallback.call(this)
})
}, 0)
return this
}
testEl = null
})(Zepto)