├── .gitignore
├── .vscode
└── launch.json
├── LICENSE
├── README.md
├── index.html
├── jsconfig.json
├── package.json
├── server
├── api.js
├── app.js
├── upload-multer.js
├── upload-multiparty.js
└── www
├── src
├── imgs
│ ├── lena_std.bmp
│ ├── lena_std.gif
│ ├── lena_std.ico
│ ├── lena_std.jpg
│ ├── lena_std.png
│ └── lena_std.tif
├── index.html
├── libs
│ ├── Zepto-1.2.0.js
│ ├── demo.css
│ ├── demo.js
│ ├── es6-promise.js
│ ├── image-compress.js
│ ├── init.js
│ ├── jQuery-3.1.0.js
│ └── whatwg-fetch.js
├── upload-blob-fetch.html
├── upload-blob-jQuery.html
├── upload-blob-xhr.html
└── upload-blob-zepto.html
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # nyc test coverage
18 | .nyc_output
19 |
20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
21 | .grunt
22 |
23 | # node-waf configuration
24 | .lock-wscript
25 |
26 | # Compiled binary addons (http://nodejs.org/api/addons.html)
27 | build/Release
28 |
29 | # Dependency directories
30 | node_modules
31 | jspm_packages
32 |
33 | # Optional npm cache directory
34 | .npm
35 |
36 | # Optional REPL history
37 | .node_repl_history
38 |
39 | # upload images
40 | src/imgs/upload/*
41 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "启动",
6 | "type": "node",
7 | "request": "launch",
8 | "program": "${workspaceRoot}/server/www",
9 | "stopOnEntry": false,
10 | "args": [],
11 | "cwd": "${workspaceRoot}",
12 | "preLaunchTask": null,
13 | "runtimeExecutable": null,
14 | "runtimeArgs": [
15 | "--nolazy"
16 | ],
17 | "env": {
18 | "NODE_ENV": "development"
19 | },
20 | "console": "internalConsole",
21 | "sourceMaps": false,
22 | "outDir": null
23 | },
24 | {
25 | "name": "附加",
26 | "type": "node",
27 | "request": "attach",
28 | "port": 5858,
29 | "address": "localhost",
30 | "restart": false,
31 | "sourceMaps": false,
32 | "outDir": null,
33 | "localRoot": "${workspaceRoot}",
34 | "remoteRoot": null
35 | },
36 | {
37 | "name": "附加到进程",
38 | "type": "node",
39 | "request": "attach",
40 | "processId": "${command.PickProcess}",
41 | "port": 5858,
42 | "sourceMaps": false,
43 | "outDir": null
44 | }
45 | ]
46 | }
47 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Jake Archibald
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # image-compress
2 | 🗜 Browser side image compress & upload example, Android 4.0 compatible.
3 |
4 | # Quick Look
5 |
6 | [https://blade254353074.github.io/image-compress/](https://blade254353074.github.io/image-compress/)
7 |
8 | This page contain the image compressing and uploading actions step by step.
9 |
10 | # How to start
11 |
12 | 1. Install dependency & start the server
13 |
14 | ```bash
15 | $ npm install
16 |
17 | $ npm run server
18 | ```
19 |
20 | 2. Open [http://localhost:8080/](http://localhost:8080/)
21 |
22 | # Features
23 |
24 | **1. File upload test server**
25 |
26 | I use two api interface to recive image file:
27 |
28 | * /api/upload/multiparty
29 |
30 | Using [expressjs/node-multiparty](https://github.com/expressjs/node-multiparty),
31 |
32 | file at `/server/upload-multiparty.js`.
33 |
34 | * /api/upload/multer
35 |
36 | Using [expressjs/multer](https://github.com/expressjs/multer),
37 |
38 | file at `/server/upload-multer.js`.
39 |
40 | And, I use [expressjs/cors](https://github.com/expressjs/cors) to make sure all the requests can be sent by browser, whatever crossing domain.
41 |
42 | **2. Browser side**
43 |
44 | * FilReader
45 | * window.URL
46 | * Canvas
47 | * Blob
48 | * BlobBuilder
49 | * atob
50 | * Uint8Array
51 | * [multipart/form-data](https://tools.ietf.org/html/rfc7578)
52 | * FormData
53 | * XMLHttpRequest
54 | * Fetch
55 |
56 | For details to see the article at [https://sebastianblade.com/browser-side-image-compress-and-upload/](https://sebastianblade.com/browser-side-image-compress-and-upload/).
57 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Browser side image compression demo
9 |
10 |
11 |
12 |
13 |
Browser side image compression steps show case:
14 |
15 | 1. Get file object from input[type="file"][accept="image/*"] control.
16 |
17 |
18 |
object File:
19 |
20 |
21 |
22 | Or get lena_std file directly:
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | 2. Use window.URL/FileReader to get image url(DataURL/window.URL), and load by Image DOM object.
33 |
34 |
35 |
dataURL/BlobURL:
36 |
37 |
38 |
39 |
40 |
41 |
42 | 3. Draw Image to the canvas.
43 |
44 |
45 |
46 |
47 | 4. Use canvas.toDataURL to compress the image, and export the base-64 encode dataURL string.
48 |
49 |
mimeType:
50 |
51 |
sourceFileSize:
52 |
53 |
CompressedFileSize:
54 |
55 |
dataURL:
56 |
57 |
58 | CompressedImage:
59 |
60 |
61 |
CompressionDuring:
62 |
63 |
64 |
81 |
82 |
83 |
84 |
85 |
86 | 5. Decode the base-64 string to binaryString by using window.atob.
87 |
88 |
Base64-ContentType:
89 |
90 |
pureBase64Data:
91 |
92 |
binaryString:
93 |
94 |
95 |
96 |
97 |
98 | 6. Concat the binaryString to multipart format string.
99 |
100 |
multipart/form-data binaryString:
101 |
102 |
103 |
104 |
105 |
106 | 7. Convert the multipart format string to ArrayBuffer by using Uint8Array.
107 |
108 |
Uint8Array Buffer:
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 | Notice: If you want to upload file to the server.
117 | Please try to start with the local server to test.
118 | https://github.com/blade254353074/image-compress
119 |
120 | 0x01. Send the arrayBuffer by XMLHttpRequest.(After step 7)
121 |
122 |
123 |
124 |
125 |
126 | 0x02. Send the blob by XMLHttpRequest and FormData.(After step 4)
127 |
128 |
129 |
130 |
131 |
132 |
133 | 0x03. Use form to upload file.(For comparison)
134 |
135 |
139 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | // 请访问 https://go.microsoft.com/fwlink/?LinkId=759670
3 | // 参阅有关 jsconfig.json 格式的文档
4 | "exclude": [
5 | "node_modules",
6 | "bower_components",
7 | "jspm_packages",
8 | "tmp",
9 | "temp"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "image-compress",
3 | "version": "1.0.0",
4 | "description": "Browser side image compress & upload example, Android 4.0 compatible.",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "browser-sync start -s --port 8080 -f \"./src,.index.html\" --no-ui",
8 | "server": "cross-env NODE_ENV=development node server/www",
9 | "test": "echo \"Error: no test specified\" && exit 1"
10 | },
11 | "keywords": [
12 | "image",
13 | "compress",
14 | "BrowserSide",
15 | "Android4.0"
16 | ],
17 | "author": "SebastianBlade",
18 | "repository": {
19 | "url": "https://github.com/blade254353074/image-compress.git"
20 | },
21 | "license": "MIT",
22 | "devDependencies": {
23 | "browser-sync": "^2.15.0",
24 | "cross-env": "^2.0.1"
25 | },
26 | "dependencies": {
27 | "body-parser": "^1.15.2",
28 | "cookie-parser": "^1.4.3",
29 | "cors": "^2.8.1",
30 | "express": "^4.14.0",
31 | "file-type": "^3.8.0",
32 | "log4js": "^0.6.38",
33 | "multer": "^1.2.0",
34 | "multiparty": "^4.1.2",
35 | "read-chunk": "^2.0.0",
36 | "shortid": "^2.2.6"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/server/api.js:
--------------------------------------------------------------------------------
1 | var express = require('express')
2 | var router = express.Router()
3 |
4 | router.post('/upload/multiparty', require('./upload-multiparty'))
5 | router.post('/upload/multer', require('./upload-multer'))
6 |
7 | module.exports = router
8 |
--------------------------------------------------------------------------------
/server/app.js:
--------------------------------------------------------------------------------
1 | var bodyParser = require('body-parser')
2 | var cookieParser = require('cookie-parser')
3 | var cors = require('cors')
4 | var express = require('express')
5 | var log4js = require('log4js')
6 | var path = require('path')
7 |
8 | var logger = log4js.getLogger('app')
9 | var httpLogger = log4js.connectLogger(log4js.getLogger('http'), { level: 'auto' })
10 | var app = express()
11 |
12 | app.use(httpLogger)
13 | app.use(bodyParser.json())
14 | app.use(bodyParser.urlencoded({ extended: true }))
15 | app.use(cookieParser())
16 | app.use(cors({
17 | origin: function (origin, callback) {
18 | callback(null, true)
19 | },
20 | credentials: true
21 | }))
22 | app.use(express.static(path.resolve(__dirname, '../src')))
23 |
24 | app.use('/api', require('./api'))
25 |
26 | // catch 404 and forward to error handler
27 | app.use((req, res, next) => {
28 | var err = new Error('Not Found')
29 | err.status = 404
30 | next(err)
31 | })
32 |
33 | // error handlers
34 |
35 | // development error handler
36 | // will print stacktrace
37 | if (app.get('env') === 'development') {
38 | app.use((err, req, res, next) => {
39 | var status = err.status || 500
40 | logger.error(err)
41 | res.status(status)
42 | res.send({
43 | message: err.message,
44 | status,
45 | stack: err.stack
46 | })
47 | })
48 | }
49 |
50 | // production error handler
51 | // no stacktraces leaked to user
52 | app.use((err, req, res, next) => {
53 | var status = err.status || 500
54 | logger.error(err)
55 | res.status(status)
56 | res.send({
57 | error: {
58 | message: err.message,
59 | status
60 | }
61 | })
62 | })
63 |
64 | module.exports = app
65 |
--------------------------------------------------------------------------------
/server/upload-multer.js:
--------------------------------------------------------------------------------
1 | var fileType = require('file-type')
2 | var fs = require('fs')
3 | var multer = require('multer')
4 | var path = require('path')
5 | var readChunk = require('read-chunk')
6 | var shortid = require('shortid')
7 |
8 | var uploadDir = path.resolve(process.cwd(), 'src/imgs/upload')
9 | var maxFilesSize = 100 * 1000 * 1000 // 100MB
10 | var storage = multer.diskStorage({
11 | destination: function (req, file, cb) {
12 | cb(null, uploadDir)
13 | },
14 | filename: function (req, file, cb) {
15 | var mimetype = file.mimetype
16 | cb(null, shortid.generate() + '.' + mimetype.substr(mimetype.lastIndexOf('/') + 1))
17 | }
18 | })
19 | var upload = multer({
20 | storage: storage,
21 | limits: {
22 | fieldSize: maxFilesSize
23 | }
24 | })
25 |
26 | function fileQualify (filePath, formats) {
27 | // https://github.com/sindresorhus/file-type
28 | var buffer = readChunk.sync(filePath, 0, 262)
29 | var type = fileType(buffer)
30 | if (formats.indexOf(type && type.ext) === -1) {
31 | unlinkFile(filePath)
32 | throw new Error('Upload error, wrong file type,just accept jpg, jpeg, gif, png.')
33 | }
34 | }
35 |
36 | function unlinkFile (filePath) {
37 | fs.unlink(filePath, function (err) {
38 | if (err) {
39 | throw (err)
40 | }
41 | })
42 | }
43 |
44 | function uploadMiddleware (req, res) {
45 | return new Promise(function (resolve, reject) {
46 | var uploader = upload.single('file')
47 | uploader(req, res, function (err) {
48 | if (err) {
49 | reject(err)
50 | }
51 | resolve(req, res)
52 | })
53 | })
54 | }
55 |
56 | module.exports = function uploadMulter (req, res, next) {
57 | Promise.resolve()
58 | .then(function () {
59 | return uploadMiddleware(req, res)
60 | })
61 | .then(function (req, res) {
62 | if (req.file) {
63 | fileQualify(req.file.path, ['jpg', 'png', 'gif'])
64 | } else {
65 | throw new Error('Upload error, empty file field.')
66 | }
67 | })
68 | .then(function () {
69 | res.send({
70 | path: '/imgs/upload/' + req.file.filename
71 | })
72 | })
73 | .catch(function (err) {
74 | err.status = err.status || 400
75 | next(err)
76 | })
77 | }
78 |
--------------------------------------------------------------------------------
/server/upload-multiparty.js:
--------------------------------------------------------------------------------
1 | var fileType = require('file-type')
2 | var fs = require('fs')
3 | var multiparty = require('multiparty')
4 | var path = require('path')
5 | var readChunk = require('read-chunk')
6 |
7 | var uploadDir = path.resolve(process.cwd(), 'src/imgs/upload')
8 | var maxFilesSize = 100 * 1000 * 1000 // 100MB
9 |
10 | function uploadMiddleware (req, res) {
11 | return new Promise(function (resolve, reject) {
12 | var form = new multiparty.Form({
13 | maxFilesSize: maxFilesSize,
14 | uploadDir: uploadDir
15 | })
16 |
17 | form.on('error', function (err) {
18 | var error = new Error(err.message)
19 | error.status = err.statusCode
20 | reject(error)
21 | })
22 |
23 | form.parse(req, function (err, fields, files) {
24 | if (err) {
25 | err.message = 'Upload error,file size over limit (100MB).'
26 | return reject(err)
27 | }
28 | resolve(files)
29 | })
30 | })
31 | }
32 |
33 | function unlinkFile (filePath) {
34 | fs.unlink(filePath, function (err) {
35 | if (err) {
36 | throw (err)
37 | }
38 | })
39 | }
40 |
41 | function fileQualify (filePath, formats) {
42 | // https://github.com/sindresorhus/file-type
43 | var buffer = readChunk.sync(filePath, 0, 262)
44 | var type = fileType(buffer)
45 | if (formats.indexOf(type && type.ext) === -1) {
46 | unlinkFile(filePath)
47 | throw new Error('Upload error, wrong file type,just accept jpg, jpeg, gif, png.')
48 | }
49 | }
50 |
51 | module.exports = function uploadMultiparty (req, res, next) {
52 | Promise.resolve()
53 | .then(function () {
54 | return uploadMiddleware(req, res)
55 | })
56 | .then(function (files) {
57 | if (!files.file) {
58 | throw new Error('Upload error, empty file field.')
59 | }
60 | return files.file[0]
61 | })
62 | .then(function (file) {
63 | const fileName = file.originalFilename
64 | const filePath = file.path // temporary file path
65 | fileQualify(filePath, ['jpg', 'png', 'gif'])
66 | return path.basename(filePath)
67 | })
68 | .then(function (filename) {
69 | res.send({
70 | path: '/imgs/upload/' + filename
71 | })
72 | })
73 | .catch(function (err) {
74 | err.status = err.status || 400
75 | next(err)
76 | })
77 | }
78 |
--------------------------------------------------------------------------------
/server/www:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var app = require('./app.js')
4 | var express = require('express')
5 | var http = require('http')
6 | var logger = require('log4js').getLogger('app')
7 |
8 | function normalizePort (val) {
9 | var port = parseInt(val, 10)
10 |
11 | if (isNaN(port)) {
12 | return val
13 | }
14 |
15 | if (port >= 0) {
16 | return port
17 | }
18 |
19 | return false
20 | }
21 |
22 | var port = normalizePort(process.env.PORT || 8080)
23 | var server = http.createServer(app)
24 |
25 | server
26 | .listen(port)
27 | .on('error', function onError (error) {
28 | if (error.syscall !== 'listen') {
29 | throw error
30 | }
31 |
32 | var bind = typeof port === 'string'
33 | ? 'Pipe ' + port
34 | : 'Port ' + port
35 |
36 | switch (error.code) {
37 | case 'EACCES':
38 | logger.error(bind + ' requires elevated privileges')
39 | process.exit(1)
40 | break
41 | case 'EADDRINUSE':
42 | logger.error(bind + ' is already in use')
43 | process.exit(1)
44 | break
45 | default:
46 | throw error
47 | }
48 | })
49 | .on('listening', function onListening () {
50 | var addr = server.address()
51 | var bind = typeof addr === 'string'
52 | ? 'pipe ' + addr
53 | : 'port ' + addr.port
54 | logger.info('Listening on ' + bind)
55 | })
56 |
--------------------------------------------------------------------------------
/src/imgs/lena_std.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blade254353074/image-compress/a899541c578b03805e5ff49fa32c84db4af96f0b/src/imgs/lena_std.bmp
--------------------------------------------------------------------------------
/src/imgs/lena_std.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blade254353074/image-compress/a899541c578b03805e5ff49fa32c84db4af96f0b/src/imgs/lena_std.gif
--------------------------------------------------------------------------------
/src/imgs/lena_std.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blade254353074/image-compress/a899541c578b03805e5ff49fa32c84db4af96f0b/src/imgs/lena_std.ico
--------------------------------------------------------------------------------
/src/imgs/lena_std.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blade254353074/image-compress/a899541c578b03805e5ff49fa32c84db4af96f0b/src/imgs/lena_std.jpg
--------------------------------------------------------------------------------
/src/imgs/lena_std.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blade254353074/image-compress/a899541c578b03805e5ff49fa32c84db4af96f0b/src/imgs/lena_std.png
--------------------------------------------------------------------------------
/src/imgs/lena_std.tif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blade254353074/image-compress/a899541c578b03805e5ff49fa32c84db4af96f0b/src/imgs/lena_std.tif
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Browser side image compression demo
9 |
10 |
11 |
12 |
13 |
Browser side image compression steps show case:
14 |
15 | 1. Get file object from input[type="file"][accept="image/*"] control.
16 |
17 |
18 |
object File:
19 |
20 |
21 |
22 | Or get lena_std file directly:
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | 2. Use window.URL/FileReader to get image url(DataURL/window.URL), and load by Image DOM object.
33 |
34 |
35 |
dataURL/BlobURL:
36 |
37 |
38 |
39 |
40 |
41 |
42 | 3. Draw Image to the canvas.
43 |
44 |
45 |
46 |
47 | 4. Use canvas.toDataURL to compress the image, and export the base-64 encode dataURL string.
48 |
49 |
mimeType:
50 |
51 |
sourceFileSize:
52 |
53 |
CompressedFileSize:
54 |
55 |
dataURL:
56 |
57 |
58 | CompressedImage:
59 |
60 |
61 |
CompressionDuring:
62 |
63 |
64 |
81 |
82 |
83 |
84 |
85 |
86 | 5. Decode the base-64 string to binaryString by using window.atob.
87 |
88 |
Base64-ContentType:
89 |
90 |
pureBase64Data:
91 |
92 |
binaryString:
93 |
94 |
95 |
96 |
97 |
98 | 6. Concat the binaryString to multipart format string.
99 |
100 |
multipart/form-data binaryString:
101 |
102 |
103 |
104 |
105 |
106 | 7. Convert the multipart format string to ArrayBuffer by using Uint8Array.
107 |
108 |
Uint8Array Buffer:
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 | Notice: If you want to upload file to the server.
117 | Please try to start with the local server to test.
118 | https://github.com/blade254353074/image-compress
119 |
120 | 0x01. Send the arrayBuffer by XMLHttpRequest.(After step 7)
121 |
122 |
123 |
124 |
125 |
126 | 0x02. Send the blob by XMLHttpRequest and FormData.(After step 4)
127 |
128 |
129 |
130 |
131 |
132 |
133 | 0x03. Use form to upload file.(For comparison)
134 |
135 |
139 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
--------------------------------------------------------------------------------
/src/libs/Zepto-1.2.0.js:
--------------------------------------------------------------------------------
1 | /* Zepto v1.2.0 - zepto event ajax form ie - zeptojs.com/license */
2 | (function(global, factory) {
3 | if (typeof define === 'function' && define.amd)
4 | define(function() { return factory(global) })
5 | else
6 | factory(global)
7 | }(this, function(window) {
8 | var Zepto = (function() {
9 | var undefined, key, $, classList, emptyArray = [], concat = emptyArray.concat, filter = emptyArray.filter, slice = emptyArray.slice,
10 | document = window.document,
11 | elementDisplay = {}, classCache = {},
12 | cssNumber = { 'column-count': 1, 'columns': 1, 'font-weight': 1, 'line-height': 1,'opacity': 1, 'z-index': 1, 'zoom': 1 },
13 | fragmentRE = /^\s*<(\w+|!)[^>]*>/,
14 | singleTagRE = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,
15 | tagExpanderRE = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
16 | rootNodeRE = /^(?:body|html)$/i,
17 | capitalRE = /([A-Z])/g,
18 |
19 | // special attributes that should be get/set via method calls
20 | methodAttributes = ['val', 'css', 'html', 'text', 'data', 'width', 'height', 'offset'],
21 |
22 | adjacencyOperators = [ 'after', 'prepend', 'before', 'append' ],
23 | table = document.createElement('table'),
24 | tableRow = document.createElement('tr'),
25 | containers = {
26 | 'tr': document.createElement('tbody'),
27 | 'tbody': table, 'thead': table, 'tfoot': table,
28 | 'td': tableRow, 'th': tableRow,
29 | '*': document.createElement('div')
30 | },
31 | readyRE = /complete|loaded|interactive/,
32 | simpleSelectorRE = /^[\w-]*$/,
33 | class2type = {},
34 | toString = class2type.toString,
35 | zepto = {},
36 | camelize, uniq,
37 | tempParent = document.createElement('div'),
38 | propMap = {
39 | 'tabindex': 'tabIndex',
40 | 'readonly': 'readOnly',
41 | 'for': 'htmlFor',
42 | 'class': 'className',
43 | 'maxlength': 'maxLength',
44 | 'cellspacing': 'cellSpacing',
45 | 'cellpadding': 'cellPadding',
46 | 'rowspan': 'rowSpan',
47 | 'colspan': 'colSpan',
48 | 'usemap': 'useMap',
49 | 'frameborder': 'frameBorder',
50 | 'contenteditable': 'contentEditable'
51 | },
52 | isArray = Array.isArray ||
53 | function(object){ return object instanceof Array }
54 |
55 | zepto.matches = function(element, selector) {
56 | if (!selector || !element || element.nodeType !== 1) return false
57 | var matchesSelector = element.matches || element.webkitMatchesSelector ||
58 | element.mozMatchesSelector || element.oMatchesSelector ||
59 | element.matchesSelector
60 | if (matchesSelector) return matchesSelector.call(element, selector)
61 | // fall back to performing a selector:
62 | var match, parent = element.parentNode, temp = !parent
63 | if (temp) (parent = tempParent).appendChild(element)
64 | match = ~zepto.qsa(parent, selector).indexOf(element)
65 | temp && tempParent.removeChild(element)
66 | return match
67 | }
68 |
69 | function type(obj) {
70 | return obj == null ? String(obj) :
71 | class2type[toString.call(obj)] || "object"
72 | }
73 |
74 | function isFunction(value) { return type(value) == "function" }
75 | function isWindow(obj) { return obj != null && obj == obj.window }
76 | function isDocument(obj) { return obj != null && obj.nodeType == obj.DOCUMENT_NODE }
77 | function isObject(obj) { return type(obj) == "object" }
78 | function isPlainObject(obj) {
79 | return isObject(obj) && !isWindow(obj) && Object.getPrototypeOf(obj) == Object.prototype
80 | }
81 |
82 | function likeArray(obj) {
83 | var length = !!obj && 'length' in obj && obj.length,
84 | type = $.type(obj)
85 |
86 | return 'function' != type && !isWindow(obj) && (
87 | 'array' == type || length === 0 ||
88 | (typeof length == 'number' && length > 0 && (length - 1) in obj)
89 | )
90 | }
91 |
92 | function compact(array) { return filter.call(array, function(item){ return item != null }) }
93 | function flatten(array) { return array.length > 0 ? $.fn.concat.apply([], array) : array }
94 | camelize = function(str){ return str.replace(/-+(.)?/g, function(match, chr){ return chr ? chr.toUpperCase() : '' }) }
95 | function dasherize(str) {
96 | return str.replace(/::/g, '/')
97 | .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
98 | .replace(/([a-z\d])([A-Z])/g, '$1_$2')
99 | .replace(/_/g, '-')
100 | .toLowerCase()
101 | }
102 | uniq = function(array){ return filter.call(array, function(item, idx){ return array.indexOf(item) == idx }) }
103 |
104 | function classRE(name) {
105 | return name in classCache ?
106 | classCache[name] : (classCache[name] = new RegExp('(^|\\s)' + name + '(\\s|$)'))
107 | }
108 |
109 | function maybeAddPx(name, value) {
110 | return (typeof value == "number" && !cssNumber[dasherize(name)]) ? value + "px" : value
111 | }
112 |
113 | function defaultDisplay(nodeName) {
114 | var element, display
115 | if (!elementDisplay[nodeName]) {
116 | element = document.createElement(nodeName)
117 | document.body.appendChild(element)
118 | display = getComputedStyle(element, '').getPropertyValue("display")
119 | element.parentNode.removeChild(element)
120 | display == "none" && (display = "block")
121 | elementDisplay[nodeName] = display
122 | }
123 | return elementDisplay[nodeName]
124 | }
125 |
126 | function children(element) {
127 | return 'children' in element ?
128 | slice.call(element.children) :
129 | $.map(element.childNodes, function(node){ if (node.nodeType == 1) return node })
130 | }
131 |
132 | function Z(dom, selector) {
133 | var i, len = dom ? dom.length : 0
134 | for (i = 0; i < len; i++) this[i] = dom[i]
135 | this.length = len
136 | this.selector = selector || ''
137 | }
138 |
139 | // `$.zepto.fragment` takes a html string and an optional tag name
140 | // to generate DOM nodes from the given html string.
141 | // The generated DOM nodes are returned as an array.
142 | // This function can be overridden in plugins for example to make
143 | // it compatible with browsers that don't support the DOM fully.
144 | zepto.fragment = function(html, name, properties) {
145 | var dom, nodes, container
146 |
147 | // A special case optimization for a single tag
148 | if (singleTagRE.test(html)) dom = $(document.createElement(RegExp.$1))
149 |
150 | if (!dom) {
151 | if (html.replace) html = html.replace(tagExpanderRE, "<$1>$2>")
152 | if (name === undefined) name = fragmentRE.test(html) && RegExp.$1
153 | if (!(name in containers)) name = '*'
154 |
155 | container = containers[name]
156 | container.innerHTML = '' + html
157 | dom = $.each(slice.call(container.childNodes), function(){
158 | container.removeChild(this)
159 | })
160 | }
161 |
162 | if (isPlainObject(properties)) {
163 | nodes = $(dom)
164 | $.each(properties, function(key, value) {
165 | if (methodAttributes.indexOf(key) > -1) nodes[key](value)
166 | else nodes.attr(key, value)
167 | })
168 | }
169 |
170 | return dom
171 | }
172 |
173 | // `$.zepto.Z` swaps out the prototype of the given `dom` array
174 | // of nodes with `$.fn` and thus supplying all the Zepto functions
175 | // to the array. This method can be overridden in plugins.
176 | zepto.Z = function(dom, selector) {
177 | return new Z(dom, selector)
178 | }
179 |
180 | // `$.zepto.isZ` should return `true` if the given object is a Zepto
181 | // collection. This method can be overridden in plugins.
182 | zepto.isZ = function(object) {
183 | return object instanceof zepto.Z
184 | }
185 |
186 | // `$.zepto.init` is Zepto's counterpart to jQuery's `$.fn.init` and
187 | // takes a CSS selector and an optional context (and handles various
188 | // special cases).
189 | // This method can be overridden in plugins.
190 | zepto.init = function(selector, context) {
191 | var dom
192 | // If nothing given, return an empty Zepto collection
193 | if (!selector) return zepto.Z()
194 | // Optimize for string selectors
195 | else if (typeof selector == 'string') {
196 | selector = selector.trim()
197 | // If it's a html fragment, create nodes from it
198 | // Note: In both Chrome 21 and Firefox 15, DOM error 12
199 | // is thrown if the fragment doesn't begin with <
200 | if (selector[0] == '<' && fragmentRE.test(selector))
201 | dom = zepto.fragment(selector, RegExp.$1, context), selector = null
202 | // If there's a context, create a collection on that context first, and select
203 | // nodes from there
204 | else if (context !== undefined) return $(context).find(selector)
205 | // If it's a CSS selector, use it to select nodes.
206 | else dom = zepto.qsa(document, selector)
207 | }
208 | // If a function is given, call it when the DOM is ready
209 | else if (isFunction(selector)) return $(document).ready(selector)
210 | // If a Zepto collection is given, just return it
211 | else if (zepto.isZ(selector)) return selector
212 | else {
213 | // normalize array if an array of nodes is given
214 | if (isArray(selector)) dom = compact(selector)
215 | // Wrap DOM nodes.
216 | else if (isObject(selector))
217 | dom = [selector], selector = null
218 | // If it's a html fragment, create nodes from it
219 | else if (fragmentRE.test(selector))
220 | dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector = null
221 | // If there's a context, create a collection on that context first, and select
222 | // nodes from there
223 | else if (context !== undefined) return $(context).find(selector)
224 | // And last but no least, if it's a CSS selector, use it to select nodes.
225 | else dom = zepto.qsa(document, selector)
226 | }
227 | // create a new Zepto collection from the nodes found
228 | return zepto.Z(dom, selector)
229 | }
230 |
231 | // `$` will be the base `Zepto` object. When calling this
232 | // function just call `$.zepto.init, which makes the implementation
233 | // details of selecting nodes and creating Zepto collections
234 | // patchable in plugins.
235 | $ = function(selector, context){
236 | return zepto.init(selector, context)
237 | }
238 |
239 | function extend(target, source, deep) {
240 | for (key in source)
241 | if (deep && (isPlainObject(source[key]) || isArray(source[key]))) {
242 | if (isPlainObject(source[key]) && !isPlainObject(target[key]))
243 | target[key] = {}
244 | if (isArray(source[key]) && !isArray(target[key]))
245 | target[key] = []
246 | extend(target[key], source[key], deep)
247 | }
248 | else if (source[key] !== undefined) target[key] = source[key]
249 | }
250 |
251 | // Copy all but undefined properties from one or more
252 | // objects to the `target` object.
253 | $.extend = function(target){
254 | var deep, args = slice.call(arguments, 1)
255 | if (typeof target == 'boolean') {
256 | deep = target
257 | target = args.shift()
258 | }
259 | args.forEach(function(arg){ extend(target, arg, deep) })
260 | return target
261 | }
262 |
263 | // `$.zepto.qsa` is Zepto's CSS selector implementation which
264 | // uses `document.querySelectorAll` and optimizes for some special cases, like `#id`.
265 | // This method can be overridden in plugins.
266 | zepto.qsa = function(element, selector){
267 | var found,
268 | maybeID = selector[0] == '#',
269 | maybeClass = !maybeID && selector[0] == '.',
270 | nameOnly = maybeID || maybeClass ? selector.slice(1) : selector, // Ensure that a 1 char tag name still gets checked
271 | isSimple = simpleSelectorRE.test(nameOnly)
272 | return (element.getElementById && isSimple && maybeID) ? // Safari DocumentFragment doesn't have getElementById
273 | ( (found = element.getElementById(nameOnly)) ? [found] : [] ) :
274 | (element.nodeType !== 1 && element.nodeType !== 9 && element.nodeType !== 11) ? [] :
275 | slice.call(
276 | isSimple && !maybeID && element.getElementsByClassName ? // DocumentFragment doesn't have getElementsByClassName/TagName
277 | maybeClass ? element.getElementsByClassName(nameOnly) : // If it's simple, it could be a class
278 | element.getElementsByTagName(selector) : // Or a tag
279 | element.querySelectorAll(selector) // Or it's not simple, and we need to query all
280 | )
281 | }
282 |
283 | function filtered(nodes, selector) {
284 | return selector == null ? $(nodes) : $(nodes).filter(selector)
285 | }
286 |
287 | $.contains = document.documentElement.contains ?
288 | function(parent, node) {
289 | return parent !== node && parent.contains(node)
290 | } :
291 | function(parent, node) {
292 | while (node && (node = node.parentNode))
293 | if (node === parent) return true
294 | return false
295 | }
296 |
297 | function funcArg(context, arg, idx, payload) {
298 | return isFunction(arg) ? arg.call(context, idx, payload) : arg
299 | }
300 |
301 | function setAttribute(node, name, value) {
302 | value == null ? node.removeAttribute(name) : node.setAttribute(name, value)
303 | }
304 |
305 | // access className property while respecting SVGAnimatedString
306 | function className(node, value){
307 | var klass = node.className || '',
308 | svg = klass && klass.baseVal !== undefined
309 |
310 | if (value === undefined) return svg ? klass.baseVal : klass
311 | svg ? (klass.baseVal = value) : (node.className = value)
312 | }
313 |
314 | // "true" => true
315 | // "false" => false
316 | // "null" => null
317 | // "42" => 42
318 | // "42.5" => 42.5
319 | // "08" => "08"
320 | // JSON => parse if valid
321 | // String => self
322 | function deserializeValue(value) {
323 | try {
324 | return value ?
325 | value == "true" ||
326 | ( value == "false" ? false :
327 | value == "null" ? null :
328 | +value + "" == value ? +value :
329 | /^[\[\{]/.test(value) ? $.parseJSON(value) :
330 | value )
331 | : value
332 | } catch(e) {
333 | return value
334 | }
335 | }
336 |
337 | $.type = type
338 | $.isFunction = isFunction
339 | $.isWindow = isWindow
340 | $.isArray = isArray
341 | $.isPlainObject = isPlainObject
342 |
343 | $.isEmptyObject = function(obj) {
344 | var name
345 | for (name in obj) return false
346 | return true
347 | }
348 |
349 | $.isNumeric = function(val) {
350 | var num = Number(val), type = typeof val
351 | return val != null && type != 'boolean' &&
352 | (type != 'string' || val.length) &&
353 | !isNaN(num) && isFinite(num) || false
354 | }
355 |
356 | $.inArray = function(elem, array, i){
357 | return emptyArray.indexOf.call(array, elem, i)
358 | }
359 |
360 | $.camelCase = camelize
361 | $.trim = function(str) {
362 | return str == null ? "" : String.prototype.trim.call(str)
363 | }
364 |
365 | // plugin compatibility
366 | $.uuid = 0
367 | $.support = { }
368 | $.expr = { }
369 | $.noop = function() {}
370 |
371 | $.map = function(elements, callback){
372 | var value, values = [], i, key
373 | if (likeArray(elements))
374 | for (i = 0; i < elements.length; i++) {
375 | value = callback(elements[i], i)
376 | if (value != null) values.push(value)
377 | }
378 | else
379 | for (key in elements) {
380 | value = callback(elements[key], key)
381 | if (value != null) values.push(value)
382 | }
383 | return flatten(values)
384 | }
385 |
386 | $.each = function(elements, callback){
387 | var i, key
388 | if (likeArray(elements)) {
389 | for (i = 0; i < elements.length; i++)
390 | if (callback.call(elements[i], i, elements[i]) === false) return elements
391 | } else {
392 | for (key in elements)
393 | if (callback.call(elements[key], key, elements[key]) === false) return elements
394 | }
395 |
396 | return elements
397 | }
398 |
399 | $.grep = function(elements, callback){
400 | return filter.call(elements, callback)
401 | }
402 |
403 | if (window.JSON) $.parseJSON = JSON.parse
404 |
405 | // Populate the class2type map
406 | $.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
407 | class2type[ "[object " + name + "]" ] = name.toLowerCase()
408 | })
409 |
410 | // Define methods that will be available on all
411 | // Zepto collections
412 | $.fn = {
413 | constructor: zepto.Z,
414 | length: 0,
415 |
416 | // Because a collection acts like an array
417 | // copy over these useful array functions.
418 | forEach: emptyArray.forEach,
419 | reduce: emptyArray.reduce,
420 | push: emptyArray.push,
421 | sort: emptyArray.sort,
422 | splice: emptyArray.splice,
423 | indexOf: emptyArray.indexOf,
424 | concat: function(){
425 | var i, value, args = []
426 | for (i = 0; i < arguments.length; i++) {
427 | value = arguments[i]
428 | args[i] = zepto.isZ(value) ? value.toArray() : value
429 | }
430 | return concat.apply(zepto.isZ(this) ? this.toArray() : this, args)
431 | },
432 |
433 | // `map` and `slice` in the jQuery API work differently
434 | // from their array counterparts
435 | map: function(fn){
436 | return $($.map(this, function(el, i){ return fn.call(el, i, el) }))
437 | },
438 | slice: function(){
439 | return $(slice.apply(this, arguments))
440 | },
441 |
442 | ready: function(callback){
443 | // need to check if document.body exists for IE as that browser reports
444 | // document ready when it hasn't yet created the body element
445 | if (readyRE.test(document.readyState) && document.body) callback($)
446 | else document.addEventListener('DOMContentLoaded', function(){ callback($) }, false)
447 | return this
448 | },
449 | get: function(idx){
450 | return idx === undefined ? slice.call(this) : this[idx >= 0 ? idx : idx + this.length]
451 | },
452 | toArray: function(){ return this.get() },
453 | size: function(){
454 | return this.length
455 | },
456 | remove: function(){
457 | return this.each(function(){
458 | if (this.parentNode != null)
459 | this.parentNode.removeChild(this)
460 | })
461 | },
462 | each: function(callback){
463 | emptyArray.every.call(this, function(el, idx){
464 | return callback.call(el, idx, el) !== false
465 | })
466 | return this
467 | },
468 | filter: function(selector){
469 | if (isFunction(selector)) return this.not(this.not(selector))
470 | return $(filter.call(this, function(element){
471 | return zepto.matches(element, selector)
472 | }))
473 | },
474 | add: function(selector,context){
475 | return $(uniq(this.concat($(selector,context))))
476 | },
477 | is: function(selector){
478 | return this.length > 0 && zepto.matches(this[0], selector)
479 | },
480 | not: function(selector){
481 | var nodes=[]
482 | if (isFunction(selector) && selector.call !== undefined)
483 | this.each(function(idx){
484 | if (!selector.call(this,idx)) nodes.push(this)
485 | })
486 | else {
487 | var excludes = typeof selector == 'string' ? this.filter(selector) :
488 | (likeArray(selector) && isFunction(selector.item)) ? slice.call(selector) : $(selector)
489 | this.forEach(function(el){
490 | if (excludes.indexOf(el) < 0) nodes.push(el)
491 | })
492 | }
493 | return $(nodes)
494 | },
495 | has: function(selector){
496 | return this.filter(function(){
497 | return isObject(selector) ?
498 | $.contains(this, selector) :
499 | $(this).find(selector).size()
500 | })
501 | },
502 | eq: function(idx){
503 | return idx === -1 ? this.slice(idx) : this.slice(idx, + idx + 1)
504 | },
505 | first: function(){
506 | var el = this[0]
507 | return el && !isObject(el) ? el : $(el)
508 | },
509 | last: function(){
510 | var el = this[this.length - 1]
511 | return el && !isObject(el) ? el : $(el)
512 | },
513 | find: function(selector){
514 | var result, $this = this
515 | if (!selector) result = $()
516 | else if (typeof selector == 'object')
517 | result = $(selector).filter(function(){
518 | var node = this
519 | return emptyArray.some.call($this, function(parent){
520 | return $.contains(parent, node)
521 | })
522 | })
523 | else if (this.length == 1) result = $(zepto.qsa(this[0], selector))
524 | else result = this.map(function(){ return zepto.qsa(this, selector) })
525 | return result
526 | },
527 | closest: function(selector, context){
528 | var nodes = [], collection = typeof selector == 'object' && $(selector)
529 | this.each(function(_, node){
530 | while (node && !(collection ? collection.indexOf(node) >= 0 : zepto.matches(node, selector)))
531 | node = node !== context && !isDocument(node) && node.parentNode
532 | if (node && nodes.indexOf(node) < 0) nodes.push(node)
533 | })
534 | return $(nodes)
535 | },
536 | parents: function(selector){
537 | var ancestors = [], nodes = this
538 | while (nodes.length > 0)
539 | nodes = $.map(nodes, function(node){
540 | if ((node = node.parentNode) && !isDocument(node) && ancestors.indexOf(node) < 0) {
541 | ancestors.push(node)
542 | return node
543 | }
544 | })
545 | return filtered(ancestors, selector)
546 | },
547 | parent: function(selector){
548 | return filtered(uniq(this.pluck('parentNode')), selector)
549 | },
550 | children: function(selector){
551 | return filtered(this.map(function(){ return children(this) }), selector)
552 | },
553 | contents: function() {
554 | return this.map(function() { return this.contentDocument || slice.call(this.childNodes) })
555 | },
556 | siblings: function(selector){
557 | return filtered(this.map(function(i, el){
558 | return filter.call(children(el.parentNode), function(child){ return child!==el })
559 | }), selector)
560 | },
561 | empty: function(){
562 | return this.each(function(){ this.innerHTML = '' })
563 | },
564 | // `pluck` is borrowed from Prototype.js
565 | pluck: function(property){
566 | return $.map(this, function(el){ return el[property] })
567 | },
568 | show: function(){
569 | return this.each(function(){
570 | this.style.display == "none" && (this.style.display = '')
571 | if (getComputedStyle(this, '').getPropertyValue("display") == "none")
572 | this.style.display = defaultDisplay(this.nodeName)
573 | })
574 | },
575 | replaceWith: function(newContent){
576 | return this.before(newContent).remove()
577 | },
578 | wrap: function(structure){
579 | var func = isFunction(structure)
580 | if (this[0] && !func)
581 | var dom = $(structure).get(0),
582 | clone = dom.parentNode || this.length > 1
583 |
584 | return this.each(function(index){
585 | $(this).wrapAll(
586 | func ? structure.call(this, index) :
587 | clone ? dom.cloneNode(true) : dom
588 | )
589 | })
590 | },
591 | wrapAll: function(structure){
592 | if (this[0]) {
593 | $(this[0]).before(structure = $(structure))
594 | var children
595 | // drill down to the inmost element
596 | while ((children = structure.children()).length) structure = children.first()
597 | $(structure).append(this)
598 | }
599 | return this
600 | },
601 | wrapInner: function(structure){
602 | var func = isFunction(structure)
603 | return this.each(function(index){
604 | var self = $(this), contents = self.contents(),
605 | dom = func ? structure.call(this, index) : structure
606 | contents.length ? contents.wrapAll(dom) : self.append(dom)
607 | })
608 | },
609 | unwrap: function(){
610 | this.parent().each(function(){
611 | $(this).replaceWith($(this).children())
612 | })
613 | return this
614 | },
615 | clone: function(){
616 | return this.map(function(){ return this.cloneNode(true) })
617 | },
618 | hide: function(){
619 | return this.css("display", "none")
620 | },
621 | toggle: function(setting){
622 | return this.each(function(){
623 | var el = $(this)
624 | ;(setting === undefined ? el.css("display") == "none" : setting) ? el.show() : el.hide()
625 | })
626 | },
627 | prev: function(selector){ return $(this.pluck('previousElementSibling')).filter(selector || '*') },
628 | next: function(selector){ return $(this.pluck('nextElementSibling')).filter(selector || '*') },
629 | html: function(html){
630 | return 0 in arguments ?
631 | this.each(function(idx){
632 | var originHtml = this.innerHTML
633 | $(this).empty().append( funcArg(this, html, idx, originHtml) )
634 | }) :
635 | (0 in this ? this[0].innerHTML : null)
636 | },
637 | text: function(text){
638 | return 0 in arguments ?
639 | this.each(function(idx){
640 | var newText = funcArg(this, text, idx, this.textContent)
641 | this.textContent = newText == null ? '' : ''+newText
642 | }) :
643 | (0 in this ? this.pluck('textContent').join("") : null)
644 | },
645 | attr: function(name, value){
646 | var result
647 | return (typeof name == 'string' && !(1 in arguments)) ?
648 | (0 in this && this[0].nodeType == 1 && (result = this[0].getAttribute(name)) != null ? result : undefined) :
649 | this.each(function(idx){
650 | if (this.nodeType !== 1) return
651 | if (isObject(name)) for (key in name) setAttribute(this, key, name[key])
652 | else setAttribute(this, name, funcArg(this, value, idx, this.getAttribute(name)))
653 | })
654 | },
655 | removeAttr: function(name){
656 | return this.each(function(){ this.nodeType === 1 && name.split(' ').forEach(function(attribute){
657 | setAttribute(this, attribute)
658 | }, this)})
659 | },
660 | prop: function(name, value){
661 | name = propMap[name] || name
662 | return (1 in arguments) ?
663 | this.each(function(idx){
664 | this[name] = funcArg(this, value, idx, this[name])
665 | }) :
666 | (this[0] && this[0][name])
667 | },
668 | removeProp: function(name){
669 | name = propMap[name] || name
670 | return this.each(function(){ delete this[name] })
671 | },
672 | data: function(name, value){
673 | var attrName = 'data-' + name.replace(capitalRE, '-$1').toLowerCase()
674 |
675 | var data = (1 in arguments) ?
676 | this.attr(attrName, value) :
677 | this.attr(attrName)
678 |
679 | return data !== null ? deserializeValue(data) : undefined
680 | },
681 | val: function(value){
682 | if (0 in arguments) {
683 | if (value == null) value = ""
684 | return this.each(function(idx){
685 | this.value = funcArg(this, value, idx, this.value)
686 | })
687 | } else {
688 | return this[0] && (this[0].multiple ?
689 | $(this[0]).find('option').filter(function(){ return this.selected }).pluck('value') :
690 | this[0].value)
691 | }
692 | },
693 | offset: function(coordinates){
694 | if (coordinates) return this.each(function(index){
695 | var $this = $(this),
696 | coords = funcArg(this, coordinates, index, $this.offset()),
697 | parentOffset = $this.offsetParent().offset(),
698 | props = {
699 | top: coords.top - parentOffset.top,
700 | left: coords.left - parentOffset.left
701 | }
702 |
703 | if ($this.css('position') == 'static') props['position'] = 'relative'
704 | $this.css(props)
705 | })
706 | if (!this.length) return null
707 | if (document.documentElement !== this[0] && !$.contains(document.documentElement, this[0]))
708 | return {top: 0, left: 0}
709 | var obj = this[0].getBoundingClientRect()
710 | return {
711 | left: obj.left + window.pageXOffset,
712 | top: obj.top + window.pageYOffset,
713 | width: Math.round(obj.width),
714 | height: Math.round(obj.height)
715 | }
716 | },
717 | css: function(property, value){
718 | if (arguments.length < 2) {
719 | var element = this[0]
720 | if (typeof property == 'string') {
721 | if (!element) return
722 | return element.style[camelize(property)] || getComputedStyle(element, '').getPropertyValue(property)
723 | } else if (isArray(property)) {
724 | if (!element) return
725 | var props = {}
726 | var computedStyle = getComputedStyle(element, '')
727 | $.each(property, function(_, prop){
728 | props[prop] = (element.style[camelize(prop)] || computedStyle.getPropertyValue(prop))
729 | })
730 | return props
731 | }
732 | }
733 |
734 | var css = ''
735 | if (type(property) == 'string') {
736 | if (!value && value !== 0)
737 | this.each(function(){ this.style.removeProperty(dasherize(property)) })
738 | else
739 | css = dasherize(property) + ":" + maybeAddPx(property, value)
740 | } else {
741 | for (key in property)
742 | if (!property[key] && property[key] !== 0)
743 | this.each(function(){ this.style.removeProperty(dasherize(key)) })
744 | else
745 | css += dasherize(key) + ':' + maybeAddPx(key, property[key]) + ';'
746 | }
747 |
748 | return this.each(function(){ this.style.cssText += ';' + css })
749 | },
750 | index: function(element){
751 | return element ? this.indexOf($(element)[0]) : this.parent().children().indexOf(this[0])
752 | },
753 | hasClass: function(name){
754 | if (!name) return false
755 | return emptyArray.some.call(this, function(el){
756 | return this.test(className(el))
757 | }, classRE(name))
758 | },
759 | addClass: function(name){
760 | if (!name) return this
761 | return this.each(function(idx){
762 | if (!('className' in this)) return
763 | classList = []
764 | var cls = className(this), newName = funcArg(this, name, idx, cls)
765 | newName.split(/\s+/g).forEach(function(klass){
766 | if (!$(this).hasClass(klass)) classList.push(klass)
767 | }, this)
768 | classList.length && className(this, cls + (cls ? " " : "") + classList.join(" "))
769 | })
770 | },
771 | removeClass: function(name){
772 | return this.each(function(idx){
773 | if (!('className' in this)) return
774 | if (name === undefined) return className(this, '')
775 | classList = className(this)
776 | funcArg(this, name, idx, classList).split(/\s+/g).forEach(function(klass){
777 | classList = classList.replace(classRE(klass), " ")
778 | })
779 | className(this, classList.trim())
780 | })
781 | },
782 | toggleClass: function(name, when){
783 | if (!name) return this
784 | return this.each(function(idx){
785 | var $this = $(this), names = funcArg(this, name, idx, className(this))
786 | names.split(/\s+/g).forEach(function(klass){
787 | (when === undefined ? !$this.hasClass(klass) : when) ?
788 | $this.addClass(klass) : $this.removeClass(klass)
789 | })
790 | })
791 | },
792 | scrollTop: function(value){
793 | if (!this.length) return
794 | var hasScrollTop = 'scrollTop' in this[0]
795 | if (value === undefined) return hasScrollTop ? this[0].scrollTop : this[0].pageYOffset
796 | return this.each(hasScrollTop ?
797 | function(){ this.scrollTop = value } :
798 | function(){ this.scrollTo(this.scrollX, value) })
799 | },
800 | scrollLeft: function(value){
801 | if (!this.length) return
802 | var hasScrollLeft = 'scrollLeft' in this[0]
803 | if (value === undefined) return hasScrollLeft ? this[0].scrollLeft : this[0].pageXOffset
804 | return this.each(hasScrollLeft ?
805 | function(){ this.scrollLeft = value } :
806 | function(){ this.scrollTo(value, this.scrollY) })
807 | },
808 | position: function() {
809 | if (!this.length) return
810 |
811 | var elem = this[0],
812 | // Get *real* offsetParent
813 | offsetParent = this.offsetParent(),
814 | // Get correct offsets
815 | offset = this.offset(),
816 | parentOffset = rootNodeRE.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset()
817 |
818 | // Subtract element margins
819 | // note: when an element has margin: auto the offsetLeft and marginLeft
820 | // are the same in Safari causing offset.left to incorrectly be 0
821 | offset.top -= parseFloat( $(elem).css('margin-top') ) || 0
822 | offset.left -= parseFloat( $(elem).css('margin-left') ) || 0
823 |
824 | // Add offsetParent borders
825 | parentOffset.top += parseFloat( $(offsetParent[0]).css('border-top-width') ) || 0
826 | parentOffset.left += parseFloat( $(offsetParent[0]).css('border-left-width') ) || 0
827 |
828 | // Subtract the two offsets
829 | return {
830 | top: offset.top - parentOffset.top,
831 | left: offset.left - parentOffset.left
832 | }
833 | },
834 | offsetParent: function() {
835 | return this.map(function(){
836 | var parent = this.offsetParent || document.body
837 | while (parent && !rootNodeRE.test(parent.nodeName) && $(parent).css("position") == "static")
838 | parent = parent.offsetParent
839 | return parent
840 | })
841 | }
842 | }
843 |
844 | // for now
845 | $.fn.detach = $.fn.remove
846 |
847 | // Generate the `width` and `height` functions
848 | ;['width', 'height'].forEach(function(dimension){
849 | var dimensionProperty =
850 | dimension.replace(/./, function(m){ return m[0].toUpperCase() })
851 |
852 | $.fn[dimension] = function(value){
853 | var offset, el = this[0]
854 | if (value === undefined) return isWindow(el) ? el['inner' + dimensionProperty] :
855 | isDocument(el) ? el.documentElement['scroll' + dimensionProperty] :
856 | (offset = this.offset()) && offset[dimension]
857 | else return this.each(function(idx){
858 | el = $(this)
859 | el.css(dimension, funcArg(this, value, idx, el[dimension]()))
860 | })
861 | }
862 | })
863 |
864 | function traverseNode(node, fun) {
865 | fun(node)
866 | for (var i = 0, len = node.childNodes.length; i < len; i++)
867 | traverseNode(node.childNodes[i], fun)
868 | }
869 |
870 | // Generate the `after`, `prepend`, `before`, `append`,
871 | // `insertAfter`, `insertBefore`, `appendTo`, and `prependTo` methods.
872 | adjacencyOperators.forEach(function(operator, operatorIndex) {
873 | var inside = operatorIndex % 2 //=> prepend, append
874 |
875 | $.fn[operator] = function(){
876 | // arguments can be nodes, arrays of nodes, Zepto objects and HTML strings
877 | var argType, nodes = $.map(arguments, function(arg) {
878 | var arr = []
879 | argType = type(arg)
880 | if (argType == "array") {
881 | arg.forEach(function(el) {
882 | if (el.nodeType !== undefined) return arr.push(el)
883 | else if ($.zepto.isZ(el)) return arr = arr.concat(el.get())
884 | arr = arr.concat(zepto.fragment(el))
885 | })
886 | return arr
887 | }
888 | return argType == "object" || arg == null ?
889 | arg : zepto.fragment(arg)
890 | }),
891 | parent, copyByClone = this.length > 1
892 | if (nodes.length < 1) return this
893 |
894 | return this.each(function(_, target){
895 | parent = inside ? target : target.parentNode
896 |
897 | // convert all methods to a "before" operation
898 | target = operatorIndex == 0 ? target.nextSibling :
899 | operatorIndex == 1 ? target.firstChild :
900 | operatorIndex == 2 ? target :
901 | null
902 |
903 | var parentInDocument = $.contains(document.documentElement, parent)
904 |
905 | nodes.forEach(function(node){
906 | if (copyByClone) node = node.cloneNode(true)
907 | else if (!parent) return $(node).remove()
908 |
909 | parent.insertBefore(node, target)
910 | if (parentInDocument) traverseNode(node, function(el){
911 | if (el.nodeName != null && el.nodeName.toUpperCase() === 'SCRIPT' &&
912 | (!el.type || el.type === 'text/javascript') && !el.src){
913 | var target = el.ownerDocument ? el.ownerDocument.defaultView : window
914 | target['eval'].call(target, el.innerHTML)
915 | }
916 | })
917 | })
918 | })
919 | }
920 |
921 | // after => insertAfter
922 | // prepend => prependTo
923 | // before => insertBefore
924 | // append => appendTo
925 | $.fn[inside ? operator+'To' : 'insert'+(operatorIndex ? 'Before' : 'After')] = function(html){
926 | $(html)[operator](this)
927 | return this
928 | }
929 | })
930 |
931 | zepto.Z.prototype = Z.prototype = $.fn
932 |
933 | // Export internal API functions in the `$.zepto` namespace
934 | zepto.uniq = uniq
935 | zepto.deserializeValue = deserializeValue
936 | $.zepto = zepto
937 |
938 | return $
939 | })()
940 |
941 | window.Zepto = Zepto
942 | window.$ === undefined && (window.$ = Zepto)
943 |
944 | ;(function($){
945 | var _zid = 1, undefined,
946 | slice = Array.prototype.slice,
947 | isFunction = $.isFunction,
948 | isString = function(obj){ return typeof obj == 'string' },
949 | handlers = {},
950 | specialEvents={},
951 | focusinSupported = 'onfocusin' in window,
952 | focus = { focus: 'focusin', blur: 'focusout' },
953 | hover = { mouseenter: 'mouseover', mouseleave: 'mouseout' }
954 |
955 | specialEvents.click = specialEvents.mousedown = specialEvents.mouseup = specialEvents.mousemove = 'MouseEvents'
956 |
957 | function zid(element) {
958 | return element._zid || (element._zid = _zid++)
959 | }
960 | function findHandlers(element, event, fn, selector) {
961 | event = parse(event)
962 | if (event.ns) var matcher = matcherFor(event.ns)
963 | return (handlers[zid(element)] || []).filter(function(handler) {
964 | return handler
965 | && (!event.e || handler.e == event.e)
966 | && (!event.ns || matcher.test(handler.ns))
967 | && (!fn || zid(handler.fn) === zid(fn))
968 | && (!selector || handler.sel == selector)
969 | })
970 | }
971 | function parse(event) {
972 | var parts = ('' + event).split('.')
973 | return {e: parts[0], ns: parts.slice(1).sort().join(' ')}
974 | }
975 | function matcherFor(ns) {
976 | return new RegExp('(?:^| )' + ns.replace(' ', ' .* ?') + '(?: |$)')
977 | }
978 |
979 | function eventCapture(handler, captureSetting) {
980 | return handler.del &&
981 | (!focusinSupported && (handler.e in focus)) ||
982 | !!captureSetting
983 | }
984 |
985 | function realEvent(type) {
986 | return hover[type] || (focusinSupported && focus[type]) || type
987 | }
988 |
989 | function add(element, events, fn, data, selector, delegator, capture){
990 | var id = zid(element), set = (handlers[id] || (handlers[id] = []))
991 | events.split(/\s/).forEach(function(event){
992 | if (event == 'ready') return $(document).ready(fn)
993 | var handler = parse(event)
994 | handler.fn = fn
995 | handler.sel = selector
996 | // emulate mouseenter, mouseleave
997 | if (handler.e in hover) fn = function(e){
998 | var related = e.relatedTarget
999 | if (!related || (related !== this && !$.contains(this, related)))
1000 | return handler.fn.apply(this, arguments)
1001 | }
1002 | handler.del = delegator
1003 | var callback = delegator || fn
1004 | handler.proxy = function(e){
1005 | e = compatible(e)
1006 | if (e.isImmediatePropagationStopped()) return
1007 | e.data = data
1008 | var result = callback.apply(element, e._args == undefined ? [e] : [e].concat(e._args))
1009 | if (result === false) e.preventDefault(), e.stopPropagation()
1010 | return result
1011 | }
1012 | handler.i = set.length
1013 | set.push(handler)
1014 | if ('addEventListener' in element)
1015 | element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
1016 | })
1017 | }
1018 | function remove(element, events, fn, selector, capture){
1019 | var id = zid(element)
1020 | ;(events || '').split(/\s/).forEach(function(event){
1021 | findHandlers(element, event, fn, selector).forEach(function(handler){
1022 | delete handlers[id][handler.i]
1023 | if ('removeEventListener' in element)
1024 | element.removeEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
1025 | })
1026 | })
1027 | }
1028 |
1029 | $.event = { add: add, remove: remove }
1030 |
1031 | $.proxy = function(fn, context) {
1032 | var args = (2 in arguments) && slice.call(arguments, 2)
1033 | if (isFunction(fn)) {
1034 | var proxyFn = function(){ return fn.apply(context, args ? args.concat(slice.call(arguments)) : arguments) }
1035 | proxyFn._zid = zid(fn)
1036 | return proxyFn
1037 | } else if (isString(context)) {
1038 | if (args) {
1039 | args.unshift(fn[context], fn)
1040 | return $.proxy.apply(null, args)
1041 | } else {
1042 | return $.proxy(fn[context], fn)
1043 | }
1044 | } else {
1045 | throw new TypeError("expected function")
1046 | }
1047 | }
1048 |
1049 | $.fn.bind = function(event, data, callback){
1050 | return this.on(event, data, callback)
1051 | }
1052 | $.fn.unbind = function(event, callback){
1053 | return this.off(event, callback)
1054 | }
1055 | $.fn.one = function(event, selector, data, callback){
1056 | return this.on(event, selector, data, callback, 1)
1057 | }
1058 |
1059 | var returnTrue = function(){return true},
1060 | returnFalse = function(){return false},
1061 | ignoreProperties = /^([A-Z]|returnValue$|layer[XY]$|webkitMovement[XY]$)/,
1062 | eventMethods = {
1063 | preventDefault: 'isDefaultPrevented',
1064 | stopImmediatePropagation: 'isImmediatePropagationStopped',
1065 | stopPropagation: 'isPropagationStopped'
1066 | }
1067 |
1068 | function compatible(event, source) {
1069 | if (source || !event.isDefaultPrevented) {
1070 | source || (source = event)
1071 |
1072 | $.each(eventMethods, function(name, predicate) {
1073 | var sourceMethod = source[name]
1074 | event[name] = function(){
1075 | this[predicate] = returnTrue
1076 | return sourceMethod && sourceMethod.apply(source, arguments)
1077 | }
1078 | event[predicate] = returnFalse
1079 | })
1080 |
1081 | event.timeStamp || (event.timeStamp = Date.now())
1082 |
1083 | if (source.defaultPrevented !== undefined ? source.defaultPrevented :
1084 | 'returnValue' in source ? source.returnValue === false :
1085 | source.getPreventDefault && source.getPreventDefault())
1086 | event.isDefaultPrevented = returnTrue
1087 | }
1088 | return event
1089 | }
1090 |
1091 | function createProxy(event) {
1092 | var key, proxy = { originalEvent: event }
1093 | for (key in event)
1094 | if (!ignoreProperties.test(key) && event[key] !== undefined) proxy[key] = event[key]
1095 |
1096 | return compatible(proxy, event)
1097 | }
1098 |
1099 | $.fn.delegate = function(selector, event, callback){
1100 | return this.on(event, selector, callback)
1101 | }
1102 | $.fn.undelegate = function(selector, event, callback){
1103 | return this.off(event, selector, callback)
1104 | }
1105 |
1106 | $.fn.live = function(event, callback){
1107 | $(document.body).delegate(this.selector, event, callback)
1108 | return this
1109 | }
1110 | $.fn.die = function(event, callback){
1111 | $(document.body).undelegate(this.selector, event, callback)
1112 | return this
1113 | }
1114 |
1115 | $.fn.on = function(event, selector, data, callback, one){
1116 | var autoRemove, delegator, $this = this
1117 | if (event && !isString(event)) {
1118 | $.each(event, function(type, fn){
1119 | $this.on(type, selector, data, fn, one)
1120 | })
1121 | return $this
1122 | }
1123 |
1124 | if (!isString(selector) && !isFunction(callback) && callback !== false)
1125 | callback = data, data = selector, selector = undefined
1126 | if (callback === undefined || data === false)
1127 | callback = data, data = undefined
1128 |
1129 | if (callback === false) callback = returnFalse
1130 |
1131 | return $this.each(function(_, element){
1132 | if (one) autoRemove = function(e){
1133 | remove(element, e.type, callback)
1134 | return callback.apply(this, arguments)
1135 | }
1136 |
1137 | if (selector) delegator = function(e){
1138 | var evt, match = $(e.target).closest(selector, element).get(0)
1139 | if (match && match !== element) {
1140 | evt = $.extend(createProxy(e), {currentTarget: match, liveFired: element})
1141 | return (autoRemove || callback).apply(match, [evt].concat(slice.call(arguments, 1)))
1142 | }
1143 | }
1144 |
1145 | add(element, event, callback, data, selector, delegator || autoRemove)
1146 | })
1147 | }
1148 | $.fn.off = function(event, selector, callback){
1149 | var $this = this
1150 | if (event && !isString(event)) {
1151 | $.each(event, function(type, fn){
1152 | $this.off(type, selector, fn)
1153 | })
1154 | return $this
1155 | }
1156 |
1157 | if (!isString(selector) && !isFunction(callback) && callback !== false)
1158 | callback = selector, selector = undefined
1159 |
1160 | if (callback === false) callback = returnFalse
1161 |
1162 | return $this.each(function(){
1163 | remove(this, event, callback, selector)
1164 | })
1165 | }
1166 |
1167 | $.fn.trigger = function(event, args){
1168 | event = (isString(event) || $.isPlainObject(event)) ? $.Event(event) : compatible(event)
1169 | event._args = args
1170 | return this.each(function(){
1171 | // handle focus(), blur() by calling them directly
1172 | if (event.type in focus && typeof this[event.type] == "function") this[event.type]()
1173 | // items in the collection might not be DOM elements
1174 | else if ('dispatchEvent' in this) this.dispatchEvent(event)
1175 | else $(this).triggerHandler(event, args)
1176 | })
1177 | }
1178 |
1179 | // triggers event handlers on current element just as if an event occurred,
1180 | // doesn't trigger an actual event, doesn't bubble
1181 | $.fn.triggerHandler = function(event, args){
1182 | var e, result
1183 | this.each(function(i, element){
1184 | e = createProxy(isString(event) ? $.Event(event) : event)
1185 | e._args = args
1186 | e.target = element
1187 | $.each(findHandlers(element, event.type || event), function(i, handler){
1188 | result = handler.proxy(e)
1189 | if (e.isImmediatePropagationStopped()) return false
1190 | })
1191 | })
1192 | return result
1193 | }
1194 |
1195 | // shortcut methods for `.bind(event, fn)` for each event type
1196 | ;('focusin focusout focus blur load resize scroll unload click dblclick '+
1197 | 'mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave '+
1198 | 'change select keydown keypress keyup error').split(' ').forEach(function(event) {
1199 | $.fn[event] = function(callback) {
1200 | return (0 in arguments) ?
1201 | this.bind(event, callback) :
1202 | this.trigger(event)
1203 | }
1204 | })
1205 |
1206 | $.Event = function(type, props) {
1207 | if (!isString(type)) props = type, type = props.type
1208 | var event = document.createEvent(specialEvents[type] || 'Events'), bubbles = true
1209 | if (props) for (var name in props) (name == 'bubbles') ? (bubbles = !!props[name]) : (event[name] = props[name])
1210 | event.initEvent(type, bubbles, true)
1211 | return compatible(event)
1212 | }
1213 |
1214 | })(Zepto)
1215 |
1216 | ;(function($){
1217 | var jsonpID = +new Date(),
1218 | document = window.document,
1219 | key,
1220 | name,
1221 | rscript = /