scale.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881
  1. ;(function() {
  2. var util = {}, events = {}, base = {}, plugins_scale = {};
  3. util = function (exports) {
  4. var SUBSTITUTE_REG = /\\?\{([^{}]+)\}/g, EMPTY = '';
  5. var RE_TRIM = /^[\s\xa0]+|[\s\xa0]+$/g, trim = String.prototype.trim;
  6. var _trim = trim ? function (str) {
  7. return str == null ? EMPTY : trim.call(str);
  8. } : function (str) {
  9. return str == null ? EMPTY : (str + '').replace(RE_TRIM, EMPTY);
  10. };
  11. function upperCase() {
  12. return arguments[1].toUpperCase();
  13. }
  14. function Empty() {
  15. }
  16. function createObject(proto, constructor) {
  17. var newProto;
  18. if (Object.create) {
  19. newProto = Object.create(proto);
  20. } else {
  21. Empty.prototype = proto;
  22. newProto = new Empty();
  23. }
  24. newProto.constructor = constructor;
  25. return newProto;
  26. }
  27. function getNodes(node, rootNode) {
  28. if (!node)
  29. return;
  30. if (node.nodeType)
  31. return [node];
  32. var rootNode = rootNode && rootNode.nodeType ? rootNode : document;
  33. if (node && typeof node === 'string') {
  34. return rootNode.querySelectorAll(node);
  35. }
  36. return;
  37. }
  38. // Useful for temporary DOM ids.
  39. var idCounter = 0;
  40. var getOffsetTop = function (el) {
  41. var offset = el.offsetTop;
  42. if (el.offsetParent != null)
  43. offset += getOffsetTop(el.offsetParent);
  44. return offset;
  45. };
  46. var getOffsetLeft = function (el) {
  47. var offset = el.offsetLeft;
  48. if (el.offsetParent != null)
  49. offset += getOffsetLeft(el.offsetParent);
  50. return offset;
  51. };
  52. var Util = {
  53. // Is a given variable an object?
  54. isObject: function (obj) {
  55. return obj === Object(obj);
  56. },
  57. isArray: Array.isArray || function (obj) {
  58. return toString.call(obj) == '[object Array]';
  59. },
  60. // Is a given array, string, or object empty?
  61. // An "empty" object has no enumerable own-properties.
  62. isEmpty: function (obj) {
  63. if (obj == null)
  64. return true;
  65. if (this.isArray(obj) || this.isString(obj))
  66. return obj.length === 0;
  67. for (var key in obj)
  68. if (this.has(obj, key))
  69. return false;
  70. return true;
  71. },
  72. mix: function (to, from, deep) {
  73. for (var i in from) {
  74. to[i] = from[i];
  75. }
  76. return to;
  77. },
  78. extend: function (r, s, px, sx) {
  79. if (!s || !r) {
  80. return r;
  81. }
  82. var sp = s.prototype, rp;
  83. // add prototype chain
  84. rp = createObject(sp, r);
  85. r.prototype = this.mix(rp, r.prototype);
  86. r.superclass = createObject(sp, s);
  87. // add prototype overrides
  88. if (px) {
  89. this.mix(rp, px);
  90. }
  91. // add object overrides
  92. if (sx) {
  93. this.mix(r, sx);
  94. }
  95. return r;
  96. },
  97. /**
  98. * test whether a string start with a specified substring
  99. * @param {String} str the whole string
  100. * @param {String} prefix a specified substring
  101. * @return {Boolean} whether str start with prefix
  102. * @member util
  103. */
  104. startsWith: function (str, prefix) {
  105. return str.lastIndexOf(prefix, 0) === 0;
  106. },
  107. /**
  108. * test whether a string end with a specified substring
  109. * @param {String} str the whole string
  110. * @param {String} suffix a specified substring
  111. * @return {Boolean} whether str end with suffix
  112. * @member util
  113. */
  114. endsWith: function (str, suffix) {
  115. var ind = str.length - suffix.length;
  116. return ind >= 0 && str.indexOf(suffix, ind) === ind;
  117. },
  118. /**
  119. * Removes the whitespace from the beginning and end of a string.
  120. * @method
  121. * @member util
  122. */
  123. trim: _trim,
  124. /**
  125. * Substitutes keywords in a string using an object/array.
  126. * Removes undef keywords and ignores escaped keywords.
  127. * @param {String} str template string
  128. * @param {Object} o json data
  129. * @member util
  130. * @param {RegExp} [regexp] to match a piece of template string
  131. */
  132. substitute: function (str, o, regexp) {
  133. if (typeof str !== 'string' || !o) {
  134. return str;
  135. }
  136. return str.replace(regexp || SUBSTITUTE_REG, function (match, name) {
  137. if (match.charAt(0) === '\\') {
  138. return match.slice(1);
  139. }
  140. return o[name] === undefined ? EMPTY : o[name];
  141. });
  142. },
  143. /**
  144. * vendors
  145. * @return { String } webkit|moz|ms|o
  146. * @memberOf Util
  147. */
  148. vendor: function () {
  149. var el = document.createElement('div').style;
  150. var vendors = [
  151. 't',
  152. 'webkitT',
  153. 'MozT',
  154. 'msT',
  155. 'OT'
  156. ], transform, i = 0, l = vendors.length;
  157. for (; i < l; i++) {
  158. transform = vendors[i] + 'ransform';
  159. if (transform in el)
  160. return vendors[i].substr(0, vendors[i].length - 1);
  161. }
  162. return false;
  163. }(),
  164. /**
  165. * add vendor to attribute
  166. * @memberOf Util
  167. * @param {String} attrName name of attribute
  168. * @return { String }
  169. **/
  170. prefixStyle: function (attrName) {
  171. if (this.vendor === false)
  172. return false;
  173. if (this.vendor === '')
  174. return attrName;
  175. return this.vendor + attrName.charAt(0).toUpperCase() + attrName.substr(1);
  176. },
  177. /**
  178. * judge if has class
  179. * @memberOf Util
  180. * @param {HTMLElement} el
  181. * @param {String} className
  182. * @return {Boolean}
  183. */
  184. hasClass: function (el, className) {
  185. return el && el.className && className && el.className.indexOf(className) != -1;
  186. },
  187. /**
  188. * add className for the element
  189. * @memberOf Util
  190. * @param {HTMLElement} el
  191. * @param {String} className
  192. */
  193. addClass: function (el, className) {
  194. if (el && className && !this.hasClass(el, className)) {
  195. el.className += ' ' + className;
  196. }
  197. },
  198. /**
  199. * remove className for the element
  200. * @memberOf Util
  201. * @param {HTMLElement} el
  202. * @param {String} className
  203. */
  204. removeClass: function (el, className) {
  205. if (el && el.className && className) {
  206. el.className = el.className.replace(className, '');
  207. }
  208. },
  209. /**
  210. * remove an element
  211. * @memberOf Util
  212. * @param {HTMLElement} el
  213. */
  214. remove: function (el) {
  215. if (!el || !el.parentNode)
  216. return;
  217. el.parentNode.removeChild(el);
  218. },
  219. /**
  220. * get offset top
  221. * @memberOf Util
  222. * @param {HTMLElement} el
  223. * @return {Number} offsetTop
  224. */
  225. getOffsetTop: getOffsetTop,
  226. /**
  227. * get offset left
  228. * @memberOf Util
  229. * @param {HTMLElement} el
  230. * @return {Number} offsetLeft
  231. */
  232. getOffsetLeft: getOffsetLeft,
  233. /**
  234. * get offset left
  235. * @memberOf Util
  236. * @param {HTMLElement} el
  237. * @param {String} selector
  238. * @param {HTMLElement} rootNode
  239. * @return {HTMLElement} parent element
  240. */
  241. findParentEl: function (el, selector, rootNode) {
  242. var rs = null, parent = null;
  243. var type = /^#/.test(selector) ? 'id' : /^\./.test(selector) ? 'class' : 'tag';
  244. var sel = selector.replace(/\.|#/g, '');
  245. if (rootNode && typeof rootNode === 'string') {
  246. rootNode = document.querySelector(rootNode);
  247. }
  248. rootNode = rootNode || document.body;
  249. if (!el || !selector)
  250. return;
  251. if (type == 'class' && el.className && el.className.match(sel)) {
  252. return el;
  253. } else if (type == 'id' && el.id && _trim(el.id) == sel) {
  254. return el;
  255. } else if (type == 'tag' && el.tagName.toLowerCase() == sel) {
  256. return el;
  257. }
  258. while (!rs) {
  259. if (parent == rootNode)
  260. break;
  261. parent = el.parentNode;
  262. if (!parent)
  263. break;
  264. if (type == 'class' && parent.className && parent.className.match(sel) || type == 'id' && parent.id && _trim(parent.id) == sel || type == 'tag' && parent.tagName && parent.tagName.toLowerCase() == sel) {
  265. rs = parent;
  266. return rs;
  267. break;
  268. } else {
  269. el = parent;
  270. }
  271. }
  272. return null;
  273. },
  274. /**
  275. * Generate a unique integer id (unique within the entire client session).
  276. * @param {String} prefix
  277. * @return {String} guid
  278. */
  279. guid: function (prefix) {
  280. var id = ++idCounter + '';
  281. return prefix ? prefix + id : id;
  282. },
  283. /**
  284. * judge if is an android os
  285. * @return {Boolean} [description]
  286. */
  287. isAndroid: function () {
  288. return /Android /.test(window.navigator.appVersion);
  289. },
  290. /**
  291. * judge if is an android device with low performance
  292. * @return {Boolean}
  293. */
  294. isBadAndroid: function () {
  295. return /Android /.test(window.navigator.appVersion) && !/Chrome\/\d/.test(window.navigator.appVersion);
  296. },
  297. px2Num: function (px) {
  298. return Number(px.replace(/px/, ''));
  299. },
  300. getNodes: getNodes,
  301. getNode: function (node, rootNode) {
  302. var nodes = getNodes(node, rootNode);
  303. return nodes && nodes[0];
  304. },
  305. stringifyStyle: function (style) {
  306. var styleStr = '';
  307. for (var i in style) {
  308. styleStr += [
  309. i,
  310. ':',
  311. style[i],
  312. ';'
  313. ].join('');
  314. }
  315. return styleStr;
  316. }
  317. };
  318. // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
  319. var names = [
  320. 'Arguments',
  321. 'Function',
  322. 'String',
  323. 'Number',
  324. 'Date',
  325. 'RegExp'
  326. ];
  327. for (var i = 0; i < names.length; i++) {
  328. Util['is' + names[i]] = function (obj) {
  329. return toString.call(obj) == '[object ' + names[i] + ']';
  330. };
  331. }
  332. if (typeof module == 'object' && module.exports) {
  333. exports = Util;
  334. } /** ignored by jsdoc **/ else {
  335. return Util;
  336. }
  337. return exports;
  338. }(util);
  339. events = function (exports) {
  340. var Util = util;
  341. // Returns a function that will be executed at most one time, no matter how
  342. // often you call it. Useful for lazy initialization.
  343. var _once = function (func) {
  344. var ran = false, memo;
  345. return function () {
  346. if (ran)
  347. return memo;
  348. ran = true;
  349. memo = func.apply(this, arguments);
  350. func = null;
  351. return memo;
  352. };
  353. };
  354. /**
  355. * @discription events
  356. * @mixin
  357. */
  358. var Events = {
  359. // Bind an event to a `callback` function. Passing `"all"` will bind
  360. // the callback to all events fired.
  361. on: function (name, callback, context) {
  362. if (!eventsApi(this, 'on', name, [
  363. callback,
  364. context
  365. ]) || !callback)
  366. return this;
  367. this._events || (this._events = {});
  368. var events = this._events[name] || (this._events[name] = []);
  369. events.push({
  370. callback: callback,
  371. context: context,
  372. ctx: context || this
  373. });
  374. return this;
  375. },
  376. // Bind an event to only be triggered a single time. After the first time
  377. // the callback is invoked, it will be removed.
  378. once: function (name, callback, context) {
  379. if (!eventsApi(this, 'once', name, [
  380. callback,
  381. context
  382. ]) || !callback)
  383. return this;
  384. var self = this;
  385. var once = _once(function () {
  386. self.off(name, once);
  387. callback.apply(this, arguments);
  388. });
  389. once._callback = callback;
  390. return this.on(name, once, context);
  391. },
  392. // Remove one or many callbacks. If `context` is null, removes all
  393. // callbacks with that function. If `callback` is null, removes all
  394. // callbacks for the event. If `name` is null, removes all bound
  395. // callbacks for all events.
  396. off: function (name, callback, context) {
  397. if (!this._events || !eventsApi(this, 'off', name, [
  398. callback,
  399. context
  400. ]))
  401. return this;
  402. // Remove all callbacks for all events.
  403. if (!name && !callback && !context) {
  404. this._events = void 0;
  405. return this;
  406. }
  407. var names = name ? [name] : Object.keys(this._events);
  408. for (var i = 0, length = names.length; i < length; i++) {
  409. name = names[i];
  410. // Bail out if there are no events stored.
  411. var events = this._events[name];
  412. if (!events)
  413. continue;
  414. // Remove all callbacks for this event.
  415. if (!callback && !context) {
  416. delete this._events[name];
  417. continue;
  418. }
  419. // Find any remaining events.
  420. var remaining = [];
  421. for (var j = 0, k = events.length; j < k; j++) {
  422. var event = events[j];
  423. if (callback && callback !== event.callback && callback !== event.callback._callback || context && context !== event.context) {
  424. remaining.push(event);
  425. }
  426. }
  427. // Replace events if there are any remaining. Otherwise, clean up.
  428. if (remaining.length) {
  429. this._events[name] = remaining;
  430. } else {
  431. delete this._events[name];
  432. }
  433. }
  434. return this;
  435. },
  436. // Trigger one or many events, firing all bound callbacks. Callbacks are
  437. // passed the same arguments as `trigger` is, apart from the event name
  438. // (unless you're listening on `"all"`, which will cause your callback to
  439. // receive the true name of the event as the first argument).
  440. trigger: function (name) {
  441. if (!this._events)
  442. return this;
  443. var args = Array.prototype.slice.call(arguments, 1);
  444. if (!eventsApi(this, 'trigger', name, args))
  445. return this;
  446. var events = this._events[name];
  447. var allEvents = this._events.all;
  448. if (events)
  449. triggerEvents(events, args);
  450. if (allEvents)
  451. triggerEvents(allEvents, arguments);
  452. return this;
  453. },
  454. // Inversion-of-control versions of `on` and `once`. Tell *this* object to
  455. // listen to an event in another object ... keeping track of what it's
  456. // listening to.
  457. listenTo: function (obj, name, callback) {
  458. var listeningTo = this._listeningTo || (this._listeningTo = {});
  459. var id = obj._listenId || (obj._listenId = Util.guid('l'));
  460. listeningTo[id] = obj;
  461. if (!callback && typeof name === 'object')
  462. callback = this;
  463. obj.on(name, callback, this);
  464. return this;
  465. },
  466. listenToOnce: function (obj, name, callback) {
  467. if (typeof name === 'object') {
  468. for (var event in name)
  469. this.listenToOnce(obj, event, name[event]);
  470. return this;
  471. }
  472. var cb = _once(function () {
  473. this.stopListening(obj, name, cb);
  474. callback.apply(this, arguments);
  475. });
  476. cb._callback = callback;
  477. return this.listenTo(obj, name, cb);
  478. },
  479. // Tell this object to stop listening to either specific events ... or
  480. // to every object it's currently listening to.
  481. stopListening: function (obj, name, callback) {
  482. var listeningTo = this._listeningTo;
  483. if (!listeningTo)
  484. return this;
  485. var remove = !name && !callback;
  486. if (!callback && typeof name === 'object')
  487. callback = this;
  488. if (obj)
  489. (listeningTo = {})[obj._listenId] = obj;
  490. for (var id in listeningTo) {
  491. obj = listeningTo[id];
  492. obj.off(name, callback, this);
  493. if (remove || Util.isEmpty(obj._events))
  494. delete this._listeningTo[id];
  495. }
  496. return this;
  497. }
  498. };
  499. // Regular expression used to split event strings.
  500. var eventSplitter = /\s+/;
  501. // Implement fancy features of the Events API such as multiple event
  502. // names `"change blur"` and jQuery-style event maps `{change: action}`
  503. // in terms of the existing API.
  504. var eventsApi = function (obj, action, name, rest) {
  505. if (!name)
  506. return true;
  507. // Handle event maps.
  508. if (typeof name === 'object') {
  509. for (var key in name) {
  510. obj[action].apply(obj, [
  511. key,
  512. name[key]
  513. ].concat(rest));
  514. }
  515. return false;
  516. }
  517. // Handle space separated event names.
  518. if (eventSplitter.test(name)) {
  519. var names = name.split(eventSplitter);
  520. for (var i = 0, length = names.length; i < length; i++) {
  521. obj[action].apply(obj, [names[i]].concat(rest));
  522. }
  523. return false;
  524. }
  525. return true;
  526. };
  527. // A difficult-to-believe, but optimized internal dispatch function for
  528. // triggering events. Tries to keep the usual cases speedy (most internal
  529. var triggerEvents = function (events, args) {
  530. var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
  531. switch (args.length) {
  532. case 0:
  533. while (++i < l)
  534. (ev = events[i]).callback.call(ev.ctx);
  535. return;
  536. case 1:
  537. while (++i < l)
  538. (ev = events[i]).callback.call(ev.ctx, a1);
  539. return;
  540. case 2:
  541. while (++i < l)
  542. (ev = events[i]).callback.call(ev.ctx, a1, a2);
  543. return;
  544. case 3:
  545. while (++i < l)
  546. (ev = events[i]).callback.call(ev.ctx, a1, a2, a3);
  547. return;
  548. default:
  549. while (++i < l)
  550. (ev = events[i]).callback.apply(ev.ctx, args);
  551. return;
  552. }
  553. };
  554. // Aliases for backwards compatibility.
  555. Events.bind = Events.on;
  556. Events.unbind = Events.off;
  557. if (typeof module == 'object' && module.exports) {
  558. exports = Events;
  559. } /** ignored by jsdoc **/ else {
  560. return Events;
  561. }
  562. return exports;
  563. }(events);
  564. base = function (exports) {
  565. var Util = util;
  566. var Events = events;
  567. /**
  568. @constructor
  569. @mixes Events
  570. */
  571. var Base = function () {
  572. };
  573. Util.mix(Base.prototype, Events);
  574. Util.mix(Base.prototype, {
  575. /**
  576. * @memberof Base
  577. * @param {object} plugin plug a plugin
  578. */
  579. plug: function (plugin) {
  580. var self = this;
  581. if (!plugin || !plugin.pluginId)
  582. return;
  583. if (!self.__plugins) {
  584. self.__plugins = [];
  585. }
  586. var __plugin = self.getPlugin(plugin.pluginId);
  587. __plugin && self.unplug(plugin.pluginId);
  588. plugin.pluginInitializer(self);
  589. self.__plugins.push(plugin);
  590. return self;
  591. },
  592. /**
  593. * @memberof Base
  594. * @param {object|string} plugin unplug a plugin by pluginId or plugin instance
  595. */
  596. unplug: function (plugin) {
  597. var self = this;
  598. if (!plugin || !self.__plugins)
  599. return;
  600. var _plugin = typeof plugin == 'string' ? self.getPlugin(plugin) : plugin;
  601. _plugin.pluginDestructor(self);
  602. for (var i = 0, l = self.__plugins.length; i < l; i++) {
  603. if (self.__plugins[i] == _plugin) {
  604. return self.__plugins.splice(i, 1);
  605. }
  606. }
  607. },
  608. /**
  609. * @memberof Base
  610. * @param {object|string} plugin get plugin by pluginId
  611. */
  612. getPlugin: function (pluginId) {
  613. var self = this;
  614. var plugins = [];
  615. if (!self.__plugins)
  616. return;
  617. for (var i = 0, l = self.__plugins.length; i < l; i++) {
  618. if (self.__plugins[i] && self.__plugins[i].pluginId == pluginId) {
  619. plugins.push(self.__plugins[i]);
  620. }
  621. }
  622. return plugins.length > 1 ? plugins : plugins[0] || null;
  623. }
  624. });
  625. if (typeof module == 'object' && module.exports) {
  626. exports = Base;
  627. } /** ignored by jsdoc **/ else {
  628. return Base;
  629. }
  630. return exports;
  631. }(base);
  632. plugins_scale = function (exports) {
  633. var Util = util, Base = base;
  634. // reduced scale rate
  635. var SCALE_RATE = 0.7;
  636. var SCALE_TO_DURATION = 300;
  637. /**
  638. * A scalable plugin for xscroll.
  639. * @constructor
  640. * @param {object} cfg
  641. * @param {number} cfg.minScale min value for scale
  642. * @param {number} cfg.maxScale max value for scale
  643. * @param {number} cfg.duration duration for scale animation
  644. * @extends {Base}
  645. */
  646. var Scale = function (cfg) {
  647. Scale.superclass.constructor.call(this, cfg);
  648. this.userConfig = Util.mix({
  649. minScale: 1,
  650. maxScale: 2,
  651. duration: SCALE_TO_DURATION
  652. }, cfg);
  653. };
  654. Util.extend(Scale, Base, {
  655. /**
  656. * a pluginId
  657. * @memberOf Scale
  658. * @type {string}
  659. */
  660. pluginId: 'scale',
  661. /**
  662. * plugin initializer
  663. * @memberOf Scale
  664. * @override Scale
  665. * @return {Infinite}
  666. */
  667. pluginInitializer: function (xscroll) {
  668. var self = this;
  669. self.scale = 1;
  670. self.xscroll = xscroll.render();
  671. self.initialContainerWidth = xscroll.containerWidth;
  672. self.initialContainerHeight = xscroll.containerHeight;
  673. self.minScale = self.userConfig.minScale || Math.max(xscroll.width / xscroll.containerWidth, xscroll.height / xscroll.containerHeight);
  674. self.maxScale = self.userConfig.maxScale || 1;
  675. self._bindEvt();
  676. return self;
  677. },
  678. /**
  679. * detroy the plugin
  680. * @memberOf Scale
  681. * @override Base
  682. * @return {Scale}
  683. */
  684. pluginDestructor: function () {
  685. var self = this;
  686. var xscroll = self.xscroll;
  687. xscroll.off('doubletap', self._doubleTapHandler, self);
  688. xscroll.off('pinchstart', self._pinchStartHandler, self);
  689. xscroll.off('pinchmove', self._pinchHandler, self);
  690. xscroll.off('pinchend pinchcancel', self._pinchEndHandler, self);
  691. return self;
  692. },
  693. _doubleTapHandler: function (e) {
  694. var self = this;
  695. var xscroll = self.xscroll;
  696. var minScale = self.userConfig.minScale;
  697. var maxScale = self.userConfig.maxScale;
  698. var duration = self.userConfig.duration;
  699. self.originX = (e.center.x - xscroll.x) / xscroll.containerWidth;
  700. self.originY = (e.center.y - xscroll.y) / xscroll.containerHeight;
  701. xscroll.scale > self.minScale ? self.scaleTo(minScale, self.originX, self.originY, duration) : self.scaleTo(maxScale, self.originX, self.originY, duration);
  702. return self;
  703. },
  704. _pinchStartHandler: function (e) {
  705. var self = this;
  706. var xscroll = self.xscroll;
  707. //disable pan gesture
  708. self.disablePan();
  709. xscroll.stop();
  710. self.isScaling = false;
  711. self.scale = xscroll.scale;
  712. self.originX = (e.center.x - xscroll.x) / xscroll.containerWidth;
  713. self.originY = (e.center.y - xscroll.y) / xscroll.containerHeight;
  714. },
  715. _pinchHandler: function (e) {
  716. var self = this;
  717. var scale = self.scale;
  718. var xscroll = self.xscroll;
  719. var originX = self.originX;
  720. var originY = self.originY;
  721. var __scale = scale * e.scale;
  722. if (__scale <= self.userConfig.minScale) {
  723. // s = 1/2 * a * 2^(s/a)
  724. __scale = 0.5 * self.userConfig.minScale * Math.pow(2, __scale / self.userConfig.minScale);
  725. }
  726. if (__scale >= self.userConfig.maxScale) {
  727. // s = 2 * a * 1/2^(a/s)
  728. __scale = 2 * self.userConfig.maxScale * Math.pow(0.5, self.userConfig.maxScale / __scale);
  729. }
  730. self._scale(__scale, originX, originY);
  731. self.xscroll.translate(xscroll.x, xscroll.y, __scale, 'e.scale', e.scale);
  732. },
  733. disablePan: function () {
  734. this.xscroll.mc.get('pan').set({ enable: false });
  735. return this;
  736. },
  737. enablePan: function () {
  738. this.xscroll.mc.get('pan').set({ enable: true });
  739. return this;
  740. },
  741. _pinchEndHandler: function (e) {
  742. var self = this;
  743. var originX = self.originX;
  744. var originY = self.originY;
  745. var xscroll = self.xscroll;
  746. if (xscroll.scale < self.minScale) {
  747. self.scaleTo(self.minScale, originX, originY, SCALE_TO_DURATION, 'ease-out', self.enablePan);
  748. } else if (xscroll.scale > self.maxScale) {
  749. self.scaleTo(self.maxScale, originX, originY, SCALE_TO_DURATION, 'ease-out', self.enablePan);
  750. } else {
  751. self.enablePan();
  752. }
  753. },
  754. _bindEvt: function () {
  755. var self = this;
  756. var xscroll = self.xscroll;
  757. xscroll.on('doubletap', self._doubleTapHandler, self);
  758. xscroll.on('pinchstart', self._pinchStartHandler, self);
  759. xscroll.on('pinchmove', self._pinchHandler, self);
  760. xscroll.on('pinchend pinchcancel', self._pinchEndHandler, self);
  761. return self;
  762. },
  763. _scale: function (scale, originX, originY) {
  764. var self = this;
  765. var xscroll = self.xscroll;
  766. var boundry = self.xscroll.boundry;
  767. if (xscroll.scale == scale || !scale)
  768. return;
  769. if (!self.isScaling) {
  770. self.scaleBegin = xscroll.scale;
  771. self.isScaling = true;
  772. self.scaleBeginX = xscroll.x;
  773. self.scaleBeginY = xscroll.y;
  774. }
  775. if (originX) {
  776. self.originX = originX;
  777. }
  778. if (originY) {
  779. self.originY = originY;
  780. }
  781. var containerWidth = scale * self.initialContainerWidth;
  782. var containerHeight = scale * self.initialContainerHeight;
  783. xscroll.containerWidth = Math.round(containerWidth > xscroll.width ? containerWidth : xscroll.width);
  784. xscroll.containerHeight = Math.round(containerHeight > xscroll.height ? containerHeight : xscroll.height);
  785. xscroll.scale = scale;
  786. var x = originX * (self.initialContainerWidth * self.scaleBegin - xscroll.containerWidth) + self.scaleBeginX;
  787. var y = originY * (self.initialContainerHeight * self.scaleBegin - xscroll.containerHeight) + self.scaleBeginY;
  788. if (x > boundry.left) {
  789. x = boundry.left;
  790. }
  791. if (y > boundry.top) {
  792. y = boundry.top;
  793. }
  794. if (x < boundry.right - xscroll.containerWidth) {
  795. x = boundry.right - xscroll.containerWidth;
  796. }
  797. if (y < boundry.bottom - xscroll.containerHeight) {
  798. y = boundry.bottom - xscroll.containerHeight;
  799. }
  800. xscroll.x = x;
  801. xscroll.y = y;
  802. },
  803. /**
  804. * scale with an animation
  805. * @memberOf Scale
  806. * @param {number} scale
  807. * @param {number} originX 0~1
  808. * @param {number} originY 0~1
  809. * @param {number} duration
  810. * @param {string} easing
  811. * @param {function} callback
  812. */
  813. scaleTo: function (scale, originX, originY, duration, easing, callback) {
  814. var self = this;
  815. var xscroll = self.xscroll;
  816. //unscalable
  817. if (xscroll.scale == scale || !scale)
  818. return;
  819. var duration = duration || SCALE_TO_DURATION;
  820. var easing = easing || 'ease-out';
  821. self.scaleStart = xscroll.scale || 1;
  822. // transitionStr = [transformStr, " ", duration , "s ", easing, " 0s"].join("");
  823. self._scale(scale, originX, originY);
  824. xscroll._animate('x', 'translateX(' + xscroll.x + 'px) scale(' + scale + ')', duration, easing, function (e) {
  825. callback && callback.call(self, e);
  826. });
  827. xscroll._animate('y', 'translateY(' + xscroll.y + 'px)', duration, easing, function (e) {
  828. callback && callback.call(self, e);
  829. });
  830. xscroll.__timers.x.timer.off('run', self.scaleHandler, self);
  831. xscroll.__timers.x.timer.off('stop', self.scaleendHandler, self);
  832. self.scaleHandler = function (e) {
  833. var _scale = (scale - self.scaleStart) * e.percent + self.scaleStart;
  834. //trigger scroll event
  835. self.trigger('scale', {
  836. scale: _scale,
  837. origin: {
  838. x: originX,
  839. y: originY
  840. }
  841. });
  842. };
  843. self.scaleendHandler = function (e) {
  844. self.isScaling = false;
  845. //enable pan gesture
  846. self.enablePan();
  847. self.trigger('scaleend', {
  848. type: 'scaleend',
  849. scale: self.scale,
  850. origin: {
  851. x: originX,
  852. y: originY
  853. }
  854. });
  855. };
  856. xscroll.__timers.x.timer.on('run', self.scaleHandler, self);
  857. xscroll.__timers.x.timer.on('stop', self.scaleendHandler, self);
  858. self.trigger('scaleanimate', {
  859. type: 'scaleanimate',
  860. scale: xscroll.scale,
  861. duration: duration,
  862. easing: easing,
  863. offset: {
  864. x: xscroll.x,
  865. y: xscroll.y
  866. },
  867. origin: {
  868. x: originX,
  869. y: originY
  870. }
  871. });
  872. }
  873. });
  874. if (typeof module == 'object' && module.exports) {
  875. exports = Scale;
  876. } /** ignored by jsdoc **/ else if (window.XScroll && window.XScroll.Plugins) {
  877. return XScroll.Plugins.Scale = Scale;
  878. }
  879. return exports;
  880. }(plugins_scale);
  881. }());