近期在优化我们公司的机票查询的价格日历(跟携程,飞猪的机票列表类似),之前是将所有切换的日期全部展示在网页中,由于默认查询日期是一年,会导致页面上存在大量的<li>,假如哪天产品要求日期默认是10年呢,估计页面要卡死了.
优化结果先看下最终的效果图:
可以看到以下两点:
1.不管怎么滚动,下面的<li>数量永远是固定的,不会随着滚动日期的增加 而累加<li>的数量,大大提高了页面的性能
2.点击了日期后,请求当天的价格,不替换<li>的情况下将"查看价格"赋值为具体的金额
技术支持:
对日期的处理主要使用的是moment.js
好了话不多说,上代码:
首先对日期做处理,设置了初始日期和截止日期,会返回这两个日期之间所有的日期,另外还会补齐已经过期的时间,和不可选择的时间,如果用户设置了当前日期,也会有标识出来,具体代码如下(下面代码要使用moment.js):
// 只负责处理日期,不负责处理业务
/*返回某时间段的所有日期数据,并格式化,并根据是否今天还是明天还是后天加了classname*/
var dateJson = []
function Calendar() {
this.settings = {
startDate: '2019-05-10', // 开始时间
endDate: '2019-10-15',// 结束时间
dateJson: dateJson, // 日期上的标识数据,class名字要和这个标识字段一摸一样,例如json里面的“折”是discount,那么css名字也叫.discount
defaultSelectDate: ['2019-07-02', '2019-07-09'] // 选中的日期
}
}
Calendar.prototype.init = function (opt) {
$.extend(this.settings, opt);
return (this.getAllDate(this.settings.startDate, this.settings.endDate));
}
Calendar.prototype.pushTag = function (yearMonthDay) {
var tags = {};
for (var i = 0; i < this.settings.dateJson.length; i++) {
if (moment(yearMonthDay).format('x') === moment(this.settings.dateJson[i].date).format('x')) {
for (var key in this.settings.dateJson[i]) {
tags[key] = this.settings.dateJson[i][key];
}
break;
}
}
return tags;
}
Calendar.prototype.setClass = function (start, end, i) { //根据日期给div设置样式
var className = '',daytype='';
if (i >= moment(start).format('x') && i <= moment(end)) { // 是否在开始和结束之间
className = 's_day';daytype='s_day';
if (moment(i).format('YYYY/MM/DD') === moment().format('YYYY/MM/DD')) { // 今天
className += ' s_today';
}
$.each(this.settings.defaultSelectDate, function (index, item) { // defaultSelectDate
if (moment(i).format('YYYY/MM/DD') === moment(item).format('YYYY/MM/DD')) {
className += ' s_curday';
}
})
} else {
className = 's_pass';daytype='s_pass';
if (moment(i).format('YYYY/MM/DD') === moment().format('YYYY/MM/DD')) {
className += ' s_today';
}
}
return {className:className,daytype:daytype};
}
Calendar.prototype.getAllDate = function (start, end) { // 获取两个日期间的所有日期数据
var sd = Number(moment(start).startOf('month').format('x')); // 本月第一天
var ed = Number(moment(end).endOf('month').format('x')); // 本月最后一天
var dataObject = {};
dataObject[sd] = {title: moment(start).format('YYYY年MM月'), date: []} // 初始第一个月
// console.log( moment(sd).weekday())
for (var w = 0; w < moment(sd).weekday(); w++) { // 对本月一号之前的周几补全。
dataObject[sd].date.push({year: '', month: '', day: '', week: w});// 如果当前月份没有存储当前天数用的数组,就创建一个空数组,如果有,就向里面添加一个空对象; (空对象是用来占位置的,用来填充月份前面的空白)
}
for (var i = sd; i <= ed;) {
var firstDay = Number(moment(i).startOf('month').format('x')); // 当月第一天;--作为每个月的唯一标示
if (moment(i).format('x') === moment(moment(i).startOf('month').format('YYYY-MM-DD')).format('x') && i !== sd) { // 如果是当月的第一天,添加下个月的数据
// console.log(i, sd)
var op = {
title: moment(i).add(1, 'days').format('YYYY年MM月'), // 下个月的第一天
date: []
}
for (var w = 0; w < moment(i).weekday(); w++) { // 对本月一号之前的周几补全。
op.date.push({year: '', month: '', day: '', week: w});// 如果当前月份没有存储当前天数用的数组,就创建一个空数组,如果有,就向里面添加一个空对象; (空对象是用来占位置的,用来填充月份前面的空白)
}
dataObject[i] = op;
}
//根据日期给div设置样式
var className = this.setClass(start, end, i).className;
var daytype = this.setClass(start, end, i).daytype;
var tag = this.pushTag(moment(i).format('YYYY/MM/DD')); // 折扣,休息等信息
var option = {
year: moment(i).format('YYYY'),
month: moment(i).format('MM'),
day: moment(i).format('DD'),
week: moment(i).weekday(),
classname: className,
daytype: daytype,
tags: tag,
date: moment(i).format('YYYY/MM/DD')
}
dataObject[firstDay].date.push(option);
i = Number(moment(i).add(1, 'days').format('x')); // 下次赋值
}
return dataObject;
}
运行下面这段代码试试:
var c1 = new Calendar();
var priceData = [
{date: '2019-02-11', price: '100'},
{date: '2019-02-12', price: '100', rest: '休'},
{date: '2019-02-13', price: '100', discount: '折'}]
console.log(c1.init({
startDate: '2019-02-06',
endDate: '2019-04-22',
defaultSelectDate: ['2019-02-09'],
dateJson: priceData
}))
结果:
1.默认是从周日开始排,2019-02-06是周五,所以前面会加5个空的年月日,
2.设置的初始日期是2019-02-06,所以2019-02-06之前和2019-04-22之后的日期都会加上s_pass的classname标识不可用日期,用以区分样式的,在2019-02-06到2019-04-22之前的日期,则都会加上s_day,表示可用日期,当前日期2019-02-09会加上s_curday
3.如果有json数据,tags里面会会带上来,展示大页面上的时候可以根据tags里面的key设置不同的样式
得到了日期数据,接下来就是滚动的代码:
// 只负责滚动的逻辑,不负责业务
function setLodaDateSwrap() {
this.CalendarBox = null;
this.leftNum = null;
this.rightNum = null;
this.settings = {
step:2, // 暂时没用
fTime:3, // 暂时没用
Calendar: 'ul',
prev: '.last-month',
next: '.next-month',
prevClick:function(){},
nextClick:function(){},
}
}
setLodaDateSwrap.prototype.init = function (obj, opt) {
$.extend(this.settings, opt);
var This = this;
this.CalendarBox = obj;
this.leftNum = -$(this.settings.Calendar).width() / 2;
// this.leftNum = -this.settings.step*$(this.settings.Calendar).find('.dayprice').width();
this.rightNum = 0;
var prevStr = this.CalendarBox + ' ' + this.settings.prev;
var nextStr = this.CalendarBox + ' ' + this.settings.next;
$(document).on('click', prevStr, function () {
if($(This.settings.Calendar).is(":animated")){
return;
}
var callbackFn = null;
// 返回布尔值,用来判断前进后退按钮是否禁用
var scroolBool = This.settings.prevClick(function(callback){
callbackFn = callback;
});
if(!scroolBool){return;}
This.swrap(This.leftNum, This.rightNum,function(){
if(callbackFn){callbackFn()}
});
This.scroolPrevBool = scroolBool;
});
$(document).on('click', nextStr, function () {
if($(This.settings.Calendar).is(":animated")){
return;
}
var callbackFn = null;
var scroolBool = This.settings.nextClick(function(callback){
callbackFn = callback;
});
if(!scroolBool){return;}
This.swrap(This.rightNum, This.leftNum,function(){
if(callbackFn){callbackFn()}
});
});
}
setLodaDateSwrap.prototype.swrap = function (num1, num2,callback) {
var This = this;
if (!$(this.settings.Calendar).is(':animated')) {
$(this.settings.Calendar).css({ left: num1 });
$(this.settings.Calendar).stop().animate({ left: num2 }, 500, function () {
$(This.settings.Calendar).css({ left: 0 });
if(callback){callback()}
});
}
}
滚动的功能做好了,然后就是滚动和日期结合由于涉及到html+css,代码较多,就不一一帖过来了,完整代码请戳https://github.com/slailcp/DatePriceScroll
像这种日期和滚动分开的好处还是很多的,例如下面这种日历形式的也可以做出来,: