当前位置: 首页 > Web前端 > CSS

如何制作“为手机而生”的日历

时间:2023-03-31 01:24:10 CSS

我之前写过一篇文章——“为手机而生”的自定义日历。一直以来童鞋们对这个插件的手势处理有些问题,所以想写一篇文章,说说它的成长史吧~看这篇文章之前,确保你已经看过日历的效果一点点~点击查看github,查看日历源码,或者在npm上搜索mob-calendar找到。1.确认需求想做日历的主要原因当然是开发过程中经常遇到。而且日历的需求千奇百怪,市面上的插件都不能满足我们产品的需求。所以,我不得不亲自动手,自己动手做。这段话好像我在创建插件级联选择器的时候也说了大家要当什么都没发生一样(?????????)。第一个问题还是处理需求:第一个问题:“日历场景有什么特点?』用户不确定自己要选择的时间点或者时间范围,需要一些基本的时间参考单位,比如”下周一”和“下周末”。用户需要查看某个时间间隔,然后有选择地选择一个时间点或时间范围,比如“周末尽量避免跳过20天的工作计划”。用户需要查看某个时间段的行为记录,比如“最近几周打卡”。当出现以上问题时,日历的时间定位优势就体现出来了。第二个问题:“日历有哪些精彩需求》』日历中有点击事件,无法预测点击事件是跳转事件还是高亮事件。日历中有选择操作,选择结果是否为a无法预测时间点或时间范围。日历的显示方式有很多种,是直接显示在文档流中,还是显示在弹出层中,无法预知。对于这些不稳定的因素,接下来,小编带大家一步步解决。2、构造函数的参数设计决定了日历的需求,那么我们来设计构造函数的参数吧~第三个问题:“日历有哪些常见的显示形式?”』纵观目前市面上常见的App,我们会发现日历的常见展示形式有两种:普通文档流形式和弹出层形式。在参数设置中,表示为设置isMask,false:普通形式,true:Layer形式。?第四个问题:“如何灵活高效地设置参数?”』1.让开发者更容易定位日期①:确定时间范围时,使用长度为3的数组,数组的每一位对应[年][月][日]如beginTime、endTime和recentTime设置②of:在特定日期指定样式或操作时,使用该日期的时间戳。比如设置beforeRenderArr时,需要传入一个符合规范的对象数组参数类型。例如stamp{Number}eg:1514822400000指定一个具体的时间戳className{String}eg:"enable"指定用户Name设置的cssclass2.灵活控制星期的排列,星期的显示格式,以及月份的显示格式①:isSundayFirst控制星期日是否放在第一列,true表示星期日放在第一列②:isChinese控制星期的显示方式,true显示中文,false显示英文③:monthType控制月份的显示格式,以January为例,0:January,1:January,2:Jan,3:January3。对最重要的滑动手势做一些配置①:angle控制滑动角度,间接控制灵敏度,推荐取值范围5-20②:isToggleBtn是否显示切换按钮,true为显示③:canViewDisabled是否可以查询不在指定范围内的月份,true为查询4.available灵活的回调乐趣开发者定义的action①:点击某个日期后的成功回调,用户定义点击后的操作。自带参数(item,arr)。item为当前点击的时间戳,arr为智能判断后连续两次点击的两个时间戳数组②:switchRender切换月份时回调,自定义切换后要执行的操作,比如发起更新数据的请求,ETC。。带有参数(年,月,cal)。year为新生成的年份,month为新生成的月份(从0开始),cal指向当前实例3,暴露在原型上,可用api名传入参数类型函数renderCallbackArr(arr){Array}渲染指定的arr,arr的格式与beforeRenderArr对象数组的格式相同。prevent()——在微信浏览器中,可能需要使用apihideBackground()来阻止默认事件——在弹出层模式的成功回调中,可能需要使用到关闭弹出层的api应该好好说明一下这个api的用途:1.传一个数组给renderCallbackArr(数组的格式和beforeRenderArr一样,不再赘述),这个方法可以把指定的时间点添加到你需要的样式中。想象一个场景:通过滑动切换,查看三个月前的签到情况,签到和未签到的日期有不同的高亮样式。显然,这个月的签到状态需要在switchRender回调中发起http请求后获取。http返回结果后,构造一个符合beforeRenderArr格式的数组,然后调用renderCallbackArr,将构造好的数组传入,即可在指定日期渲染指定的className。//举个栗子?switchRender:function(year,month,cal){console.log('计算机识别:year:'+year+'month:'+month);$.ajax({url:'xxxx',type:'GET',数据:{applyYear:year,applyMonth:(month+1),},success:function(newArr){cal.renderCallbackArr(newArr);}})}2.使用prevent()的场景不应该太多。主要是防止微信浏览器默认滑动。//这是prevent方法的源码prevent:function(e){e.preventDefault();},3.使用hideBackground()的场景一般在弹出层模式的success回调中。想象一个场景:触发日历弹窗后,如果只想【选择一个时间点】,点击某个日期后直接调用hideBackground()隐藏弹窗即可。如果要【选择某个时间间隔】,可以在确定第二个时间点后调用hideBackground()隐藏弹层。当然,不收起弹性层也是可以的。4、如何使用5个DOM实现无限滑动其实我在写第一版日历的时候,采用的方案是在生成新的月份后,不断的在body上追加dom。不过当时的商业场景比较简单,生存下来只用了10个月。但显然如果有100个月,我的方法显然行不通。因此,需要让dom可以复用,实现无限滑动。思考第五个问题:“无限滑动需要多少个dom?”』首先要明确的是,这里所说的一个dom是一个月。每次切换月份,如下图切换用月份包裹的dom。假设当前月份是【2017年9月】。由于滑动是实时的,当我的手指从右向左滑动的过程中,[October2017]就会逐渐暴露出来。考虑一种特殊情况:以签到为例。有一条2017年10月的签到记录,如果用户松开手指,停止2017年10月,签到记录的高亮样式突然闪了一下,让用户感觉很不舒服。为了避免这种情况,需要在当前月份为【2017年9月】时渲染【2017年10月】的高亮样式。左边的[August2017]也是如此,所以至少要渲染完整的三个月数据高亮,所以我们得出结论,这个月至少有3个dom,这三个dom有即使用高亮样式渲染,也不会实时显示幻灯片后的任何变化。但是为什么最后要用5个dom才能实现无限滑动呢?参考swiper的效果,为了让三个dom两边的extremedom实时正常滑动。所以在头部和尾部分别添加一个dom,这样一共需要5个dom才能实现无限滑动。如下图所示,绿色线框部分就是最初分析的3个dom。?想想第6个问题:“头尾作为padding的dom应该显示在哪个月份?”』直接参考滑动效果即可得到答案。我举个例子来说明一下:首先考虑以下情况:手势操作:从右向左连续滑动操作结果:连续查看下个月下面是图例,红色箭头更新操作:当前入口页面以2017年9月为例:初始状态:??紫色数字是代表月份dom的下标,同一个下标对应的月份也相同。中间的1、2、3对应我之前说的-----【至少要提前渲染3个月的dom】。为什么月份的开头和结尾都是3和1?假设我们现在不限制5个dom,而是无限个dom,那么代表月份dom的下标组合将是:1,2,3,1,2,3,1,2,3,1,2,3......我们以一个1,2,3为中心,得到连续5个月的dom,那么得到的下标组合为:1,2,[3,1,2,3,1],2,3,1,2,3...看不懂没关系,看完你就懂了。思考第七个问题:“为了配合无限滑动,如何控制显示的月份?”』其实以后更新月份数据需要取dom下标,所以尝试在下标数组[3,1,2,3,1]中寻找规律。我发现这个下标循环是一个3的循环,我可以通过对3取模得到每个位置的dom下标。现在我要对这个下标做一个小改动。我要把3改成0,也就是[0,1,2,0,1]道理很简单,在计算滑动距离的时候配合dom下标和translateX更方便。即滑动到最左边的月份dom时,月份dom的translateX值为0,可以对应下标0%3的结果。这样,这个下标就直接和translateX挂钩了。那么,以初始月份为2017年9月为例,最终的初始化结果为:??接下来,从右向左滑动查看下个月。touchend之后,操作如下:??当你滑动到最右边的月份dom时(其实只要滑动到border,做同样的处理),在touchstart中执行一个特殊的操作:当touchstart时,立即translate3d到它的dom下标同月:比如上面的[2017.11]已经到最右边了,下次滑动touchstart的时候再定位到下图所示的位置:???这是无限滑动的核心原理。当然也可以继续滑动:???以此类推,无限向左滑动也是类似的。5、为什么需要预测用户手势从上面介绍的无限滑动原理,可以大致感觉到,滑动距离是通过控制中间灰色矩形相对于手机屏幕的translateX来决定的。如何控制translateX的值来实现滑动效果,不是本期的重点。重点是,想想第八个问题:“如果我只在日历的dom区域控制translateX,当我想滑动整个页面的时候,我滑动不出来,怎么办?”』假设下图中的蓝色曲线代表用户的滑动曲线:当用户的滑动曲线为A时,用户的意图明显是将页面拉上去;当用户的滑动曲线为B时,用户的意图很明显是查看之前的月份,但实际上,如果仅仅通过控制translateX的值来实现滑动效果,无论是曲线A还是B,都会被考虑如想查看上个月的内容???也就是说,如果控制了translateX,那么,在占据文档流巨大面积的dom范围内,永远无法上下滑动.这是绝对不允许的。所以我们需要预测手势,实现在日历的dom范围内,不仅可以上下滑动,还可以左右滑动。效果如下:考虑问题9:“有没有一种简单的方法来预测手势?”』比如前面提到的【滑动曲线A和B】的例子图中,如果以绿线为标准,则斜率小于绿线的曲线被归类为与滑动曲线B相同,左右滑动斜率大于绿线。是否可以归类为与滑动曲线A一样的上下滑动?但实际上,用户的手势曲线一般是下面橙色的曲线。。。而且用户手势的斜率必须在touchmove中实时计算(为什么?当然是为了实时滑动),所以最后,依靠斜率来预测判断用户手势的思路到这里就结束了。6、如何用微积分来预测用户手势思考第十个问题:“能否通过整合用户手势来解决问题?”』用户的手势实际上是一个弧线。目前仅考虑从左下角滑动到右上角的情况,可以简化第一象限的用户手势曲线。如下图所示,我们从微积分的概念出发,得到如下结论。?????首先看中间的红色矩形。这个红色的长方形是一个细长的长方形夸张放大的长方形。它的宽度是△X,它的高度是△Y。通过touchmove实时计算每张幻灯片的△X和△Y,然后累加面积。面积的累加实际上是直接按照△X×△Y的正负结果累加,从而将第一象限的手势推广到所有象限的手势。计算手势的核心代码如下,其中cal指向的是当前实例:????????我们可以利用用户手势的弯曲区域来量化用户手势操作。但量化就是量化。我怎么知道我量化的结果是上下滑动还是左右滑动?所以就像计算坡度时的标准线(绿线)一样,必须有一个标准区域。思考第十题:“如何计算标准面积?”』如下图所示,我们有3条曲线,这3条曲线和X轴围成的面积就是我们努力量化的结果。其中:蓝色曲线包围的区域就是我们理想的标准区域。虽然我们不知道如何计算黄色曲线围成的面积大于标准面积,但是我们会判断所有大于蓝色曲线的量化曲线都是【用户尝试上下滑动】围成的面积绿色曲线小于标准面积,我们会判断所有小于蓝色曲线的量化曲线为【用户试图左右滑动】???问题又回来了,如何计算标准面积?看上图可以发现有一个明显的蓝色角度A,和实例化的参数角度是一回事。开发者可以通过控制angle的值来控制标准区域的大小(角度的单位是°)。当然,通过我的测试,angle的值在[5,20]中是最好的。源码是如何通过开发者传入的角度来计算标准面积的?首先,我会将用户的角度转换为tan值。????为什么我们需要tan值,因为我可以根据△X计算出△Y=△X*tanA。????所以标准面积也可以通过累加得到。????此时,我们可以通过将用户手势的面积与标准面积进行比较,得到一个理想的预测。通过预期,用户可以在页面的任意位置滑动时感到舒适。结语Github地址:自定义日历插件“为移动而生”https://github.com/AppianZ/calendar欢迎大家提出宝贵建议和技术交流?(????????)我是加宝Appian,可爱的TheAlgorithmic和尚妹纸(??????)