mui.picker.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. /**
  2. * 选择列表插件
  3. * varstion 2.0.0
  4. * by Houfeng
  5. * Houfeng@DCloud.io
  6. */
  7. (function($, window, document, undefined) {
  8. var MAX_EXCEED = 30;
  9. var VISIBLE_RANGE = 90;
  10. var DEFAULT_ITEM_HEIGHT = 40;
  11. var BLUR_WIDTH = 10;
  12. var rad2deg = $.rad2deg = function(rad) {
  13. return rad / (Math.PI / 180);
  14. };
  15. var deg2rad = $.deg2rad = function(deg) {
  16. return deg * (Math.PI / 180);
  17. };
  18. var platform = navigator.platform.toLowerCase();
  19. var userAgent = navigator.userAgent.toLowerCase();
  20. // var isIos = (userAgent.indexOf('iphone') > -1 ||
  21. // userAgent.indexOf('ipad') > -1 ||
  22. // userAgent.indexOf('ipod') > -1) &&
  23. // (platform.indexOf('iphone') > -1 ||
  24. // platform.indexOf('ipad') > -1 ||
  25. // platform.indexOf('ipod') > -1);
  26. //alert(isIos);
  27. var Picker = $.Picker = function(holder, options) {
  28. var self = this;
  29. self.holder = holder;
  30. self.options = options || {};
  31. self.init();
  32. self.initInertiaParams();
  33. self.calcElementItemPostion(true);
  34. self.bindEvent();
  35. };
  36. Picker.prototype.findElementItems = function() {
  37. var self = this;
  38. self.elementItems = [].slice.call(self.holder.querySelectorAll('li'));
  39. return self.elementItems;
  40. };
  41. Picker.prototype.init = function() {
  42. var self = this;
  43. self.list = self.holder.querySelector('ul');
  44. self.findElementItems();
  45. self.height = self.holder.offsetHeight;
  46. self.r = self.height / 2 - BLUR_WIDTH;
  47. self.d = self.r * 2;
  48. self.itemHeight = self.elementItems.length > 0 ? self.elementItems[0].offsetHeight : DEFAULT_ITEM_HEIGHT;
  49. self.itemAngle = parseInt(self.calcAngle(self.itemHeight * 0.8));
  50. self.hightlightRange = self.itemAngle / 2;
  51. self.visibleRange = VISIBLE_RANGE;
  52. self.beginAngle = 0;
  53. self.beginExceed = self.beginAngle - MAX_EXCEED;
  54. self.list.angle = self.beginAngle;
  55. // if (isIos) {
  56. // self.list.style.webkitTransformOrigin = "center center " + self.r + "px";
  57. // }
  58. };
  59. Picker.prototype.calcElementItemPostion = function(andGenerateItms) {
  60. var self = this;
  61. if (andGenerateItms) {
  62. self.items = [];
  63. }
  64. self.elementItems.forEach(function(item) {
  65. var index = self.elementItems.indexOf(item);
  66. self.endAngle = self.itemAngle * index;
  67. item.angle = self.endAngle;
  68. // item.style.webkitTransformOrigin = "center center -" + self.r + "px";
  69. // item.style.webkitTransform = "translateZ(" + self.r + "px) rotateX(" + (-self.endAngle) + "deg)";
  70. item.style.webkitTransform = "rotateX(" + (-self.endAngle) + "deg) translateZ(" + self.r + "px)";
  71. if (andGenerateItms) {
  72. var dataItem = {};
  73. dataItem.text = item.innerHTML || '';
  74. dataItem.value = item.getAttribute('data-value') || dataItem.text;
  75. self.items.push(dataItem);
  76. }
  77. });
  78. self.endExceed = self.endAngle + MAX_EXCEED;
  79. self.calcElementItemVisibility(self.beginAngle);
  80. };
  81. Picker.prototype.calcAngle = function(c) {
  82. var self = this;
  83. var a = b = parseFloat(self.r);
  84. //直径的整倍数部分直接乘以 180
  85. c = Math.abs(c); //只算角度不关心正否值
  86. var intDeg = parseInt(c / self.d) * 180;
  87. c = c % self.d;
  88. //余弦
  89. var cosC = (a * a + b * b - c * c) / (2 * a * b);
  90. var angleC = intDeg + rad2deg(Math.acos(cosC));
  91. return angleC;
  92. };
  93. Picker.prototype.calcElementItemVisibility = function(angle) {
  94. var self = this;
  95. self.elementItems.forEach(function(item) {
  96. var difference = Math.abs(item.angle - angle);
  97. if (difference < self.hightlightRange) {
  98. item.classList.add('highlight');
  99. } else if (difference < self.visibleRange) {
  100. item.classList.add('visible');
  101. item.classList.remove('highlight');
  102. } else {
  103. item.classList.remove('highlight');
  104. item.classList.remove('visible');
  105. }
  106. });
  107. };
  108. Picker.prototype.setAngle = function(angle) {
  109. var self = this;
  110. self.list.angle = angle;
  111. self.list.style.webkitTransform = "perspective(1000px) rotateY(0deg) rotateX(" + angle + "deg)";
  112. self.calcElementItemVisibility(angle);
  113. };
  114. Picker.prototype.bindEvent = function() {
  115. var self = this;
  116. var lastAngle = 0;
  117. var startY = null;
  118. var isPicking = false;
  119. self.holder.addEventListener($.EVENT_START, function(event) {
  120. isPicking = true;
  121. event.preventDefault();
  122. self.list.style.webkitTransition = '';
  123. startY = (event.changedTouches ? event.changedTouches[0] : event).pageY;
  124. lastAngle = self.list.angle;
  125. self.updateInertiaParams(event, true);
  126. }, false);
  127. self.holder.addEventListener($.EVENT_END, function(event) {
  128. isPicking = false;
  129. event.preventDefault();
  130. self.startInertiaScroll(event);
  131. }, false);
  132. self.holder.addEventListener($.EVENT_CANCEL, function(event) {
  133. isPicking = false;
  134. event.preventDefault();
  135. self.startInertiaScroll(event);
  136. }, false);
  137. self.holder.addEventListener($.EVENT_MOVE, function(event) {
  138. if (!isPicking) {
  139. return;
  140. }
  141. event.preventDefault();
  142. var endY = (event.changedTouches ? event.changedTouches[0] : event).pageY;
  143. var dragRange = endY - startY;
  144. var dragAngle = self.calcAngle(dragRange);
  145. var newAngle = dragRange > 0 ? lastAngle - dragAngle : lastAngle + dragAngle;
  146. if (newAngle > self.endExceed) {
  147. newAngle = self.endExceed
  148. }
  149. if (newAngle < self.beginExceed) {
  150. newAngle = self.beginExceed
  151. }
  152. self.setAngle(newAngle);
  153. self.updateInertiaParams(event);
  154. }, false);
  155. //--
  156. self.list.addEventListener('tap', function(event) {
  157. elementItem = event.target;
  158. if (elementItem.tagName == 'LI') {
  159. self.setSelectedIndex(self.elementItems.indexOf(elementItem), 200);
  160. }
  161. }, false);
  162. };
  163. Picker.prototype.initInertiaParams = function() {
  164. var self = this;
  165. self.lastMoveTime = 0;
  166. self.lastMoveStart = 0;
  167. self.stopInertiaMove = false;
  168. };
  169. Picker.prototype.updateInertiaParams = function(event, isStart) {
  170. var self = this;
  171. var point = event.changedTouches ? event.changedTouches[0] : event;
  172. if (isStart) {
  173. self.lastMoveStart = point.pageY;
  174. self.lastMoveTime = event.timeStamp || Date.now();
  175. self.startAngle = self.list.angle;
  176. } else {
  177. var nowTime = event.timeStamp || Date.now();
  178. if (nowTime - self.lastMoveTime > 300) {
  179. self.lastMoveTime = nowTime;
  180. self.lastMoveStart = point.pageY;
  181. }
  182. }
  183. self.stopInertiaMove = true;
  184. };
  185. Picker.prototype.startInertiaScroll = function(event) {
  186. var self = this;
  187. var point = event.changedTouches ? event.changedTouches[0] : event;
  188. /**
  189. * 缓动代码
  190. */
  191. var nowTime = event.timeStamp || Date.now();
  192. var v = (point.pageY - self.lastMoveStart) / (nowTime - self.lastMoveTime); //最后一段时间手指划动速度
  193. var dir = v > 0 ? -1 : 1; //加速度方向
  194. var deceleration = dir * 0.0006 * -1;
  195. var duration = Math.abs(v / deceleration); // 速度消减至0所需时间
  196. var dist = v * duration / 2; //最终移动多少
  197. var startAngle = self.list.angle;
  198. var distAngle = self.calcAngle(dist) * dir;
  199. //----
  200. var srcDistAngle = distAngle;
  201. if (startAngle + distAngle < self.beginExceed) {
  202. distAngle = self.beginExceed - startAngle;
  203. duration = duration * (distAngle / srcDistAngle) * 0.6;
  204. }
  205. if (startAngle + distAngle > self.endExceed) {
  206. distAngle = self.endExceed - startAngle;
  207. duration = duration * (distAngle / srcDistAngle) * 0.6;
  208. }
  209. //----
  210. if (distAngle == 0) {
  211. self.endScroll();
  212. return;
  213. }
  214. self.scrollDistAngle(nowTime, startAngle, distAngle, duration);
  215. };
  216. Picker.prototype.scrollDistAngle = function(nowTime, startAngle, distAngle, duration) {
  217. var self = this;
  218. self.stopInertiaMove = false;
  219. (function(nowTime, startAngle, distAngle, duration) {
  220. var frameInterval = 13;
  221. var stepCount = duration / frameInterval;
  222. var stepIndex = 0;
  223. (function inertiaMove() {
  224. if (self.stopInertiaMove) return;
  225. var newAngle = self.quartEaseOut(stepIndex, startAngle, distAngle, stepCount);
  226. self.setAngle(newAngle);
  227. stepIndex++;
  228. if (stepIndex > stepCount - 1 || newAngle < self.beginExceed || newAngle > self.endExceed) {
  229. self.endScroll();
  230. return;
  231. }
  232. setTimeout(inertiaMove, frameInterval);
  233. })();
  234. })(nowTime, startAngle, distAngle, duration);
  235. };
  236. Picker.prototype.quartEaseOut = function(t, b, c, d) {
  237. return -c * ((t = t / d - 1) * t * t * t - 1) + b;
  238. };
  239. Picker.prototype.endScroll = function() {
  240. var self = this;
  241. if (self.list.angle < self.beginAngle) {
  242. self.list.style.webkitTransition = "150ms ease-out";
  243. self.setAngle(self.beginAngle);
  244. } else if (self.list.angle > self.endAngle) {
  245. self.list.style.webkitTransition = "150ms ease-out";
  246. self.setAngle(self.endAngle);
  247. } else {
  248. var index = parseInt((self.list.angle / self.itemAngle).toFixed(0));
  249. self.list.style.webkitTransition = "100ms ease-out";
  250. self.setAngle(self.itemAngle * index);
  251. }
  252. self.triggerChange();
  253. };
  254. Picker.prototype.triggerChange = function(force) {
  255. var self = this;
  256. setTimeout(function() {
  257. var index = self.getSelectedIndex();
  258. var item = self.items[index];
  259. if ($.trigger && (index != self.lastIndex || force === true)) {
  260. $.trigger(self.holder, 'change', {
  261. "index": index,
  262. "item": item
  263. });
  264. //console.log('change:' + index);
  265. }
  266. self.lastIndex = index;
  267. typeof force === 'function' && force();
  268. }, 0);
  269. };
  270. Picker.prototype.correctAngle = function(angle) {
  271. var self = this;
  272. if (angle < self.beginAngle) {
  273. return self.beginAngle;
  274. } else if (angle > self.endAngle) {
  275. return self.endAngle;
  276. } else {
  277. return angle;
  278. }
  279. };
  280. Picker.prototype.setItems = function(items) {
  281. var self = this;
  282. self.items = items || [];
  283. var buffer = [];
  284. self.items.forEach(function(item) {
  285. if (item !== null && item !== undefined) {
  286. buffer.push('<li>' + (item.text || item) + '</li>');
  287. }
  288. });
  289. self.list.innerHTML = buffer.join('');
  290. self.findElementItems();
  291. self.calcElementItemPostion();
  292. self.setAngle(self.correctAngle(self.list.angle));
  293. self.triggerChange(true);
  294. };
  295. Picker.prototype.getItems = function() {
  296. var self = this;
  297. return self.items;
  298. };
  299. Picker.prototype.getSelectedIndex = function() {
  300. var self = this;
  301. return parseInt((self.list.angle / self.itemAngle).toFixed(0));
  302. };
  303. Picker.prototype.setSelectedIndex = function(index, duration, callback) {
  304. var self = this;
  305. self.list.style.webkitTransition = '';
  306. var angle = self.correctAngle(self.itemAngle * index);
  307. if (duration && duration > 0) {
  308. var distAngle = angle - self.list.angle;
  309. self.scrollDistAngle(Date.now(), self.list.angle, distAngle, duration);
  310. } else {
  311. self.setAngle(angle);
  312. }
  313. self.triggerChange(callback);
  314. };
  315. Picker.prototype.getSelectedItem = function() {
  316. var self = this;
  317. return self.items[self.getSelectedIndex()];
  318. };
  319. Picker.prototype.getSelectedValue = function() {
  320. var self = this;
  321. return (self.items[self.getSelectedIndex()] || {}).value;
  322. };
  323. Picker.prototype.getSelectedText = function() {
  324. var self = this;
  325. return (self.items[self.getSelectedIndex()] || {}).text;
  326. };
  327. Picker.prototype.setSelectedValue = function(value, duration, callback) {
  328. var self = this;
  329. for (var index in self.items) {
  330. var item = self.items[index];
  331. if (item.value == value) {
  332. self.setSelectedIndex(index, duration, callback);
  333. return;
  334. }
  335. }
  336. };
  337. if ($.fn) {
  338. $.fn.picker = function(options) {
  339. //遍历选择的元素
  340. this.each(function(i, element) {
  341. if (element.picker) return;
  342. if (options) {
  343. element.picker = new Picker(element, options);
  344. } else {
  345. var optionsText = element.getAttribute('data-picker-options');
  346. var _options = optionsText ? JSON.parse(optionsText) : {};
  347. element.picker = new Picker(element, _options);
  348. }
  349. });
  350. return this[0] ? this[0].picker : null;
  351. };
  352. //自动初始化
  353. $.ready(function() {
  354. $('.mui-picker').picker();
  355. });
  356. }
  357. })(window.mui || window, window, document, undefined);
  358. //end