前言
小伙伴们好,很久不见了。最近工作进入正常期了,所以慢慢的悠闲的时间久没有了,所以不能每天水一篇了。
最近也在听师傅(http://home.cnblogs.com/u/aaronjs/)的教导开始阅读jquery源码了,怎么说呢,阅读的效果其实不是太好。
一来是时间断断续续的没有接上,今天读完明天又忘了,到第三天再读的话,就很多都忘记了;
二来是jquery其实还是有一定难度,加之篇幅也很长,所以读起来还是有一点吃力(我甚至有时候有种想睡的感觉),过了2星期才陆陆续续把core读完,结果很多都无法理解,再加油吧。
反正今年的目标就是把jquery读懂,时间多,不着急了。
时间比较紧未做兼容处理,请使用手机/或者使用chrome开启touch功能查看,后期补上兼容方案,以及修复BUG
关于工作
最近工作上需要在我们的网页上加入一些动画:
① 页面的切入切出的转场动画
② 模仿一个iphone的日历控件
转场动画做的时候其实碰到了很多坑,而且最后做出的效果也一般,因为既有的框架与dom结构已经出来了好久了,改不得,而且就算改了效果也不能保证好,所以暂时放下
这里说的仿iphone日历控件,不如说模仿一个单选框来的实在,而且我这里说是插件,完全就算标题党了,各位可以忽视,所以今日正题吧。
iphone的感觉
第一步我们要找到iphone的感觉,那么iphone是个什么感觉呢:
大概是这么个感觉,但是我做出来却变成了这种感觉,将就着看吧:
总体思路
PS:现在其实正在写功能代码,所以,现在我们是边写边做的,思路乱了各位请包涵
测试代码
这个代码很烂,各位就不要看了,我都不知道怎么写出来的,后面点整理吧,里面的CSS不想写就直接拿别人的用了
View Code
demo请按照图示观看
http://sandbox.runjs.cn/show/dkf9dkwq
关于iscroll
最开始我们总是喜欢找一些已经存在了的解决方案来试试,这样就比较简单了,但是iscroll有一定问题就是他太大了。
最简单的压缩了都快10k了,所以直接给毙了,这里暂时就用不到他了。
zepto的touch事件
我是一个喜欢偷懒的人,我看着zepto有touch事件,本来想拿来直接用用谁知道。。。
View Code
这个就是zepto的touch事件,可以看到他只是对touchend有所监控,会触发一点点事件,所以和我们没有一毛钱关系
在此我便认命的自己敲下了以下代码:
1 document.addEventListener("touchstart", touchStart, false);
2 document.addEventListener("touchend", touchEnd, false);
3 document.addEventListener("touchmove", touchMove, false);
鼠标拖动
该功能的第一个技术点,便是元素跟着鼠标移动,拖到哪里就是哪里,这个大家都比较熟悉了,就不多说
touchend
当拖动结束时,我们要做很多后续的工作:
① 让选项进入既定的轨道
② 是否具有动画效果
③ 动画效果的步长等
PS:开始觉得有几个点可以说说,结果真的写出来却无话可说了,哎。。。。。。
经过以上版本,我们的粗制滥造版便出现了,就是以上的代码。
封装
于是我们就可以在这个基础上整理代码,封装起来了,请看下一步。
整理封装
首先,我们虽然写的不是插件,但是还是应该让他有插件的样子,来点简单的修饰吧。
经过一阶段的休整,我们的代码成了这个样子了:
View Code
http://sandbox.runjs.cn/show/biousc1u
其中动画步长可以自己调节,选一个自己认为合适的,下面接着更新。
设置/获取
View Code
我们这里简单看看代码:
复制代码
1 (function () {
2 //!!!由于动画原因,获取值可能会出现不准确的情况,比如正在动画却已经取值了
3 //所以设置了一个冷却时间,在冷却时间的情况下设置值等操作不能进行
4 //为了保证唯一性,全部使用index作为索引算了
5 var ScrollRadio = function (opts) {
6 opts = opts || {};
7 //容器元素
8 this.wrapper = opts.wrapper || $(document);
9 this.body = [
10 '<div class="cui-roller">',
11 '<ul class="ul-list" style=" position: absolute; width: 100%; z-index: 2; " >',
12 '</ul>',
13 '<div class="cui-mask"></div>',
14 '<div class="cui-lines"> </div>',
15 '</div>'
16 ].join('');
17 this.body = $(this.body);
18
19 //真正拖动的元素(现在是ul)
20 this.dragEl = this.body.find('.ul-list');
21 //数据源
22 this.data = opts.data || [];
23 this._changed = opts.changed || null;
24 //当前选项索引默认选择2项
25 this.selectedIndex = 1;
26
27 //当前选项值
28 // this.key = '';
29 //当前选项显示的值
30 // this.value = '';
31
32 /*
33 定位实际需要用到的信息
34 暂时不考虑水平移动吧
35 */
36 this.itemHeight = 0; //单个item高度
37 this.dragHeight = 0; //拖动元素高度
38 this.dragTop = 0; //拖动元素top
39 this.animateParam = [10, 6, 2, 1, 0, 0, 0, 0, 0, 0, 0]; //动画参数
40 this.timeGap = 0; //时间间隔
41 this.touchTime = 0; //开始时间
42 this.moveAble = false; //是否正在移动
43 this.moveState = 'up'; //移动状态,up right down left
44 this.oTop = 0; //拖动前的top值
45 this.curTop = 0; //当前容器top
46 this.mouseY = 0; //鼠标第一次点下时相对父容器的位置
47 this.cooling = false; //是否处于冷却时间
48
49 this.init();
50 };
51 ScrollRadio.prototype = {
52 constructor: ScrollRadio,
53 init: function () {
54 this.initItem();
55 this.wrapper.append(this.body);
56 this.initEventParam();
57 this.bindEvent();
58
59 return this;
60 },
61 //增加数据
62 initItem: function () {
63 var _tmp, _data, i, k;
64 for (var i in this.data) {
65 _data = this.data[i]
66 _tmp = $('<li>' + (_data.val == undefined ? i : _data.val) + '</li>');
67 _tmp.attr('data-index', i);
68 for (k in _data) {
69 _tmp.attr('data-' + k, _data[k]);
70 }
71 this.dragEl.append(_tmp);
72 }
73 },
74 //初始化事件需要用到的参数信息
75 initEventParam: function () {
76 var offset = this.dragEl.offset();
77 var itemOffset = this.dragEl.find('li').eq(0).offset();
78 this.itemHeight = itemOffset.height
79 this.dragTop = offset.top;
80 this.dragHeight = this.dragEl[0].scrollHeight;
81 var s = '';
82 },
83 bindEvent: function () {
84 var scope = this;
85 document.addEventListener("touchstart", function (e) {
86 scope.touchStart.call(scope, e);
87 }, false);
88 document.addEventListener("touchend", function (e) {
89 scope.touchEnd.call(scope, e);
90 }, false);
91 document.addEventListener("touchmove", function (e) {
92 scope.touchMove.call(scope, e);
93 }, false);
94 },
95 touchStart: function (e) {
96 if (this.cooling) return false; //冷却时间不能开始
97
98 //需要判断是否是拉取元素,此处需要递归验证,这里暂时不管
99 //!!!!!!!!此处不严谨
100 var el = $(e.srcElement).parent(), pos;
101 if (el.hasClass('ul-list')) {
102 this.moveAble = true;
103
104 this.touchTime = e.timeStamp;
105 //获取鼠标信息
106 pos = this.getMousePos(e.changedTouches[0]);
107 //注意,此处是相对位置,注意该处还与动画有关,所以高度必须动态计算
108 //可以设置一个冷却时间参数,但想想还是算了
109 //最后还是使用了冷却时间
110 // var top = parseFloat(this.dragEl.css('top')) || 0;
111 // this.mouseY = pos.top - top;
112 this.mouseY = pos.top - this.curTop;
113 this.moveAble = true;
114 }
115 },
116 touchMove: function (e) {
117 if (!this.moveAble) return false;
118 var pos = this.getMousePos(e.changedTouches[0]);
119 //先获取相对容器的位置,在将两个鼠标位置相减
120 this.curTop = pos.top - this.mouseY;
121 this.dragEl.css('top', this.curTop + 'px');
122 e.preventDefault();
123 },
124 touchEnd: function (e) {
125 if (!this.moveAble) return false;
126 this.cooling = true; //开启冷却时间
127
128 //时间间隔
129 var scope = this;
130 this.timeGap = e.timeStamp - this.touchTime;
131 var flag = this.oTop <= this.curTop ? 1 : -1; //判断是向上还是向下滚动
132 var flag2 = this.curTop > 0 ? 1 : -1; //这个会影响后面的计算结果
133 this.moveState = flag > 0 ? 'up' : 'down';
134 var ih = parseFloat(this.itemHeight);
135 var ih1 = ih / 2;
136
137 var top = Math.abs(this.curTop);
138 var mod = top % ih;
139 top = (parseInt(top / ih) * ih + (mod > ih1 ? ih : 0)) * flag2;
140
141 var step = parseInt(this.timeGap / 50);
142 step = step > 0 ? step : 0;
143 var speed = this.animateParam[step] || 0;
144 var increment = speed * ih * flag
145 top += increment;
146 //!!!此处动画可能导致数据不同步,后期改造需要加入冷却时间
147 if (this.oTop != this.curTop) {
148 this.dragEl.animate({
149 top: top + 'px'
150 }, 100 + (speed * 20), 'ease-out', function () {
151 var _top = top, t = false; ;
152 if (top > ih) {
153 _top = ih;
154 t = true;
155 }
156 if (top < 0 && (top + scope.dragHeight < ih * 2)) {
157 t = true;
158 _top = (scope.dragHeight - ih * 2) * (-1);
159 }
160 if (t) {
161 scope.dragEl.animate({
162 top: _top + 'px'
163 }, 10, 'ease-in-out', function () {
164 scope.oTop = _top;
165 scope.curTop = _top;
166 scope.cooling = false; //关闭冷却时间
167 scope.onTouchEnd();
168 });
169 } else {
170 scope.cooling = false; //关闭冷却时间
171 scope.oTop = top;
172 scope.curTop = top;
173 scope.onTouchEnd();
174 }
175 });
176 } else {
177 this.cooling = false; //关闭冷却时间
178 this.onTouchEnd();
179 }
180 this.moveAble = false;
181 },
182 onTouchEnd: function () {
183 var i = parseInt((this.curTop - this.itemHeight) / parseFloat(this.itemHeight));
184 this.selectedIndex = Math.abs(i);
185 var secItem = this.data[this.selectedIndex];
186 //触发变化事件
187 var changed = this._changed;
188 if (changed && typeof changed == 'function') {
189 changed.call(this, secItem);
190 }
191 console.log(this.selectedIndex, secItem);
192 },
193 setKey: function (k) { },
194 setVal: function (v) { },
195 setIndex: function (i) {
196 var i = parseInt(i);
197 if (i >= this.data.length || i < 0) return false;
198
199 this.selectedIndex = i;
200 this.curTop = (i * this.itemHeight * (-1) + this.itemHeight);
201 this.dragEl.css('top', this.curTop + 'px');
202 },
203 getSelected: function () {
204 return this.data[this.selectedIndex];
205 },
206 getByKey: function (k) { },
207 getByVal: function (v) { },
208 getByIndex: function (i) { },
209 //获取鼠标信息
210 getMousePos: function (event) {
211 var top, left;
212 top = Math.max(document.body.scrollTop, document.documentElement.scrollTop);
213 left = Math.max(document.body.scrollLeft, document.documentElement.scrollLeft);
214 return {
215 top: top + event.clientY,
216 left: left + event.clientX
217 };
218 }
219 };
220 window.ScrollRadio = ScrollRadio;
221
222 })();
复制代码
以上代码基本框架都出来了,也有注释,有兴趣的朋友自己看看,我们现在进行最后一步了,将日历搞上去,老夫来不起了。。。
日历插件:)
http://sandbox.runjs.cn/show/prii13pm
View Code
今天时间用多了,老夫有点来不起了,我们下次再完善吧。。。。。。
结语
若是各位觉得有用的话不妨用一用,发现什么BUG请留言,我下次一并更新,BUG应该很多的。
本文转自叶小钗博客园博客,原文链接http://www.cnblogs.com/yexiaochai/p/3322477.html,如需转载请自行联系原作者