/** * 时间轴插件,自写 * @author 杨黄林 * @version 1.0 */ ; (function ($) { if (!Date.prototype.hasOwnProperty('format')) { Date.prototype.format = function (fmt) { var o = { "M+": this.getMonth() + 1, //月份 "d+": this.getDate(), //日 "h+": this.getHours() % 12 === 0 ? 12 : this.getHours() % 12, //小时 "H+": this.getHours(), //小时 "m+": this.getMinutes(), //分 "s+": this.getSeconds(), //秒 "q+": Math.floor((this.getMonth() + 3) / 3), //季度 "S": this.getMilliseconds() //毫秒 }; var week = { "0": "/u65e5", "1": "/u4e00", "2": "/u4e8c", "3": "/u4e09", "4": "/u56db", "5": "/u4e94", "6": "/u516d" }; if (/(y+)/.test(fmt)) { fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length)); } if (/(E+)/.test(fmt)) { fmt = fmt.replace(RegExp.$1, ((RegExp.$1.length > 1) ? (RegExp.$1.length > 2 ? "/u661f/u671f" : "/u5468") : "") + week[this.getDay() + ""]); } for (var k in o) { if (new RegExp("(" + k + ")").test(fmt)) { fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); } } return fmt; } } /** * 1毫秒 * @type {number} */ var millseconds = 1; /** * 1秒=1000毫秒 * @type {number} */ var secondWithMillseconds = 1000 * millseconds; /** * 1分=60秒 * @type {number} */ var minuteWithMillSeconds = 60 * secondWithMillseconds; /** * 1小时=60分 * @type {number} */ var hourWithMillseconds = 60 * minuteWithMillSeconds; /** * 每个标签宽度为20像素,具体看timeline.css中.timeline-label span的宽度 * @type {number} */ var labelWidth = 20; /** * 时间提示的宽度,具体看timeline.css中.time-tips-time的宽度 * @type {number} */ var tipsWidth = 130; /** * 刻度类型,写json便于调用 * @type {{hour: string, minute: string}} */ var minScale = { minute: 'minute', hour: 'hour' } var defaults = { minScale: minScale.minute,//最小刻度:minute or hour realtime: true,//是否实时刷新时间轴:true or false。为true时,若realtimeDate是当天,则时间轴上的时间进度随系统时间变化而变化 limit: true,//是否限制可拖动刻度范围:true or false。为true时,只能在当天开始到realtimeDate范围内拖动 date: new Date(),//时间轴时间,会自动取当天的0点到第二天的0作为时间轴刻度 startHour: '00', endHour: '00', realtimeDate: new Date(),//实时时间,限制拖动范围开启后,只能在当天0点到这个时间段之间拖动 loadDataDelay: 1000,//加载数据时延时。即时间轴进度改变时,延迟多久加载数据 autoPlayDuration: 100,//自动播放间隔,越小越快,最小100 animationDuration: 100,//动画时间,越小越快。建议小于等于autoPlayDuration loadData: function (time) {//加载数据,time为时间字符串,格式为yyyy-MM-dd HH:mm }, autoPlayLoadData: function (time) {//自动播放时加载数据,这里最好不要使用异步。需要自动播放的数据提前加载到页面,后续新增的也是,放到页面对象中扩展改数据集合。如果一定要异步,需要保证其使用时间一定是小于autoPlayDuration } }; /** * 插件方法 * @param containerElement 容器对象 * @param options 插件参数 */ var timeline = function (containerElement, options) { this.$containerElement = $(containerElement); this.options = options; this.startedAutoRefreshLimitScale = false; if (this.options.autoPlayDuration < 100) this.options.autoPlayDuration = 100; //将日期格式化为当天0点 this.options.date = new Date(this.options.date.format('yyyy-MM-dd 00:00:00')); /** * 1、算出初始化时,以毫秒为单位的时间。包括当天0时的事件、当天24时(第二天0时)的时间、当前的时间 */ this.calculateTime(); /** * 2、根据算出的时间以及时间轴上刻度类型,算出初始化时,总刻度数、可拖动刻度数 */ this.calculateScaleNum(); /** * 3、根据初始化时计算出的刻度数,初始化插件DOM节点 */ this.initDom(); /** * 4、通过插件节点获取时间轴部分的宽度,根据刻度类型和刻度数,计算出每个刻度的宽度 */ this.initFixedWidth(); /** * 5、根据可拖动刻度数,计算出可拖动刻度的总宽度 */ this.calculateLimitWidth(); /** * 6、绑定鼠标事件 */ this.initEvent(); /** * 开启自动刷新可拖动刻度功能 */ if (this.options.realtime) { this.autoRefreshLimitScale(); } else { this.calculateProgress(this.limitScaleNum(), true); } var that = this; window.addEventListener('resize', function () { that.resize(); }, true); } timeline.prototype = { resize: function () { this.calculateScaleNum(); this.initFixedWidth(); // 偏移量即为已过去时间的时间轴长度,除以固定的每个刻度的宽度,得到已过的刻度数 var sacleNum = Math.round(this.$containerElement.find('.timeline-line-passed').width() / this.scaleWidth); this.showTimeTips(sacleNum); if (this.options.realtime) { this.autoRefreshLimitScale(); } }, /** * 获取开始、结束、当前时间相对于当天0时0分0秒时的毫秒数 */ calculateTime: function () { if (!this.isSameDay(this.options.date, this.options.realtimeDate)) { this.options.date.setTime(new Date(this.options.realtimeDate.format('yyyy-MM-dd 00:00:00')).getTime()); } var date = this.options.date; var scaleYear = date.getFullYear(); var scaleMonth = date.getMonth(); var scaleDate = date.getDate(); var today = new Date(); if (this.isSameDay(this.options.date, today)) { //当天,则每次计算当前时间时,从系统时间获取 this.currentTime = new Date( today.getFullYear(), today.getMonth(), today.getDate(), today.getHours(), today.getMinutes(), 0).getTime(); this.options.realtimeDate.setTime(this.currentTime); } else { //不是当天,则每次计算时间前让其时间根据刻度类型加1个刻度的时间毫秒数 if (this.startedAutoRefreshLimitScale) { if (this.options.minScale === minScale.minute) { this.options.realtimeDate.setTime(this.options.realtimeDate.getTime() + minuteWithMillSeconds); } else { this.options.realtimeDate.setTime(this.options.realtimeDate.getTime() + hourWithMillseconds); } } this.currentTime = new Date( this.options.realtimeDate.getFullYear(), this.options.realtimeDate.getMonth(), this.options.realtimeDate.getDate(), this.options.realtimeDate.getHours(), this.options.realtimeDate.getMinutes(), 0).getTime(); } this.startTime = new Date(scaleYear, scaleMonth, scaleDate, parseInt(this.options.startHour), 0, 0,).getTime(); if (parseInt(this.options.endHour) === 0 || parseInt(this.options.endHour) === 24) { this.endTime = new Date(scaleYear, scaleMonth, scaleDate + 1, parseInt(this.options.endHour), 0, 0,).getTime(); } else { this.endTime = new Date(scaleYear, scaleMonth, scaleDate, parseInt(this.options.endHour), 0, 0,).getTime(); } }, /** * 根据刻度类型,以及开始、结束、当前时间与当前0时0分0秒的时间差(毫秒数),计算出有多少刻度(包括总刻度以及可拖动刻度) */ calculateScaleNum: function () { var timeDiffWithMillseconds = this.endTime - this.startTime; var limitTimeDiffWithMillseconds = this.currentTime - this.startTime; var minutes, hours, limitMinutes, limitHours; if (this.options.minScale === minScale.minute) { minutes = Math.floor(timeDiffWithMillseconds / minuteWithMillSeconds); hours = Math.floor(timeDiffWithMillseconds / hourWithMillseconds); limitMinutes = Math.floor(limitTimeDiffWithMillseconds / minuteWithMillSeconds); limitHours = Math.floor(limitTimeDiffWithMillseconds / hourWithMillseconds); } else { hours = Math.floor(timeDiffWithMillseconds / hourWithMillseconds); limitHours = Math.floor(limitTimeDiffWithMillseconds / hourWithMillseconds); } this.scale = { minutes: minutes, hours: hours, limitMinutes: limitMinutes, limitHours: limitHours }; }, /** * 初始化dom结构 */ initDom: function () { //伪类不能js修改样式,因此必须添加一个style标签 this.$containerElement.append( [ '' ].join('') ); this.$containerElement.append( [ '
', ' ', '
' ].join('') ); this.$containerElement.append( [ '
', '
', ' ', ' ', '
', '
', '
', '
', '
', '
', '
', '
' ].join('') ); var $timelineLabel = this.$containerElement.find('.timeline-label'); for (var i = 0; i <= this.scale.hours; i++) { $timelineLabel.append('' + (parseInt(this.options.startHour) + i) + ''); } }, /** * 根据刻度刻度类型、刻度数,以及时间轴时间进度部分宽度,计算出每个刻度的固定宽度 */ initFixedWidth: function () { var timelineWidth = this.$containerElement.find('.timeline-line').width(); var scaleWidth; if (this.options.minScale === minScale.minute) { scaleWidth = timelineWidth / this.scale.minutes; } else { scaleWidth = timelineWidth / this.scale.hours; } this.scaleWidth = scaleWidth; this.timelineWidth = timelineWidth; }, /** * 根据刻度类型、可拖动刻度数,计算出可拖动刻度的总宽度占时间轴刻度部分总宽度的百分比 */ calculateLimitWidth: function () { var timelineLineLimitWidth; if (this.options.minScale === minScale.minute) { timelineLineLimitWidth = this.scale.limitMinutes * this.scaleWidth; } else { timelineLineLimitWidth = this.scale.limitHours * this.scaleWidth; } this.timelineLineLimitWidth = timelineLineLimitWidth; this.$containerElement.find('.timeline-line-limit').css('width', timelineLineLimitWidth / this.timelineWidth * 100 + '%'); }, /** * 两个日期是否为同一天 * @param dateOne 日期一 * @param dateTwo 日期二 * @returns {boolean} true or false */ isSameDay: function (dateOne, dateTwo) { var dateAString = new Date(dateOne).format('yyyy-MM-dd'); var dateBString = new Date(dateTwo).format('yyyy-MM-dd'); return dateAString === dateBString; }, /** * 定时刷新时间信息、刻度信息、可拖动刻度宽度百分比 */ autoRefreshLimitScale: function () { var that = this; var autoRefreshTimerDuration; if (this.startedAutoRefreshLimitScale === false) { this.calculateProgress(this.limitScaleNum(), true); if (this.isSameDay(this.options.date, new Date())) { if (this.options.minScale === minScale.minute) { autoRefreshTimerDuration = minuteWithMillSeconds - new Date().getSeconds() * secondWithMillseconds; } else { autoRefreshTimerDuration = hourWithMillseconds - new Date().getMinutes() * minuteWithMillSeconds; } } else { if (this.options.minScale === minScale.minute) { autoRefreshTimerDuration = minuteWithMillSeconds; } else { autoRefreshTimerDuration = hourWithMillseconds; } } } else { if (this.options.minScale === minScale.minute) { autoRefreshTimerDuration = minuteWithMillSeconds; } else { autoRefreshTimerDuration = hourWithMillseconds; } this.calculateTime(); this.calculateScaleNum(); this.calculateLimitWidth(); } // 若已播放刻度数和刷新后的(可拖动刻度数-1),说明刷新刻度前刻度在可用刻度的最末或开头 if (this.startedAutoRefreshLimitScale && (this.playedScaleNum() === this.limitScaleNum() - 1 || (this.options.minScale === minScale.minute && this.scale.limitMinutes === 1) || (this.options.minScale === minScale.hour && this.scale.limitHours === 1))) { this.calculateProgress(this.limitScaleNum(), true); } var autoRefreshTimer = setTimeout(function () { clearTimeout(autoRefreshTimer); that.autoRefreshLimitScale(); }, autoRefreshTimerDuration); this.startedAutoRefreshLimitScale = true; }, /** * 初始化dom容器上的事件 */ initEvent: function () { var that = this; this.$containerElement.on('mousemove.timelineLine', '.timeline-line', function (e) { // 鼠标移动到刻度轴上时,停止播放时间轴时间 that.stopPlay(); that.startedMouse = true; // 获取鼠标在时间轴轴线上的x坐标偏移量 var offsetX = e.originalEvent.offsetX; // 偏移量即为已过去时间的时间轴长度,除以固定的每个刻度的宽度,得到已过的刻度数 var sacleNum = Math.round(offsetX / that.scaleWidth); // 如果已过刻度大于可拖动刻度,就不把那一部分展示出来了 if (that.options.limit && sacleNum > that.limitScaleNum()) return; // 计算并展示时间轴进度 that.calculateProgress(sacleNum, true); }).on('mouseover.timelineLabelSpan', '.timeline-label span', function () { that.stopPlay(); that.startedMouse = true; // 获取当前刻度标签所在位置索引 var index = $(this).index(); var sacleNum; // 根据刻度类型,计算实际刻度。因为展示的是小时,所以如果刻度是minute类型,则需要乘以60(1小时=60分钟) if (that.options.minScale === minScale.minute) sacleNum = index * 60 else sacleNum = index; // 如果已过刻度大于可拖动刻度,就不把那一部分展示出来了 if (that.options.limit && sacleNum > that.limitScaleNum()) return; // 计算并展示时间轴进度 that.calculateProgress(sacleNum, true); }).on('mouseout.timelineContent', '.timeline-content', function (t) { // 鼠标移除后隐藏时间提示 // that.$containerElement.find('.time-tips').addClass('time-tips-hide'); }).on('click.timelineButton', '.timeline-button', function () { var $this = $(this).children('span'); var play; if ($this.is('.timeline-play')) { // 如果按钮上有timeline-play样式,说明显示的播放按钮,即没有播放时间轴 play = false; } else if ($this.is('.timeline-pause')) { // 如果按钮上有timeline-pause样式,说明显示的暂停按钮,即正在播放时间轴 play = true; } if (!play) { // 如果没播放时间轴,点击播放后则自动播放时间轴 if (that.playedScaleNum() > that.limitScaleNum() && !that.options.limit) that.startPlay(0); else that.startPlay(that.playedScaleNum()); } else { // 否则暂停播放 that.stopPlay(); } // 切换播放按钮样式 that.togglePlay(!play); }); }, /** * 根据刻度类型,实时获取可拖动刻度数 * @returns {number} 可拖动刻度数 */ limitScaleNum: function () { var limitScaleNum; if (this.options.minScale === minScale.minute) { limitScaleNum = this.scale.limitMinutes; } else { limitScaleNum = this.scale.limitHours; } return limitScaleNum; }, /** * 切换播放状态 * @param play 是否在播放 */ togglePlay: function (play) { var $playSpan = this.$containerElement.find('.timeline-button span'); if (play) { $playSpan.removeClass('timeline-play').addClass('timeline-pause'); } else { $playSpan.removeClass('timeline-pause').addClass('timeline-play'); } }, showTimeTips: function (scaleNum) { var percent = scaleNum * this.scaleWidth / this.timelineWidth; //因为多次计算的误差,可能出现大于1或小于0的情况,需要限制最大为1,最小为0 if (percent > 1) percent = 1; else if (percent < 0) percent = 0; //将当前刻度时间显示出来 this.$containerElement.find('.time-tips-time').text(this.timeString); this.$containerElement.find('.time-tips').css('left', percent * this.timelineWidth - (tipsWidth / 2) + labelWidth / 2); }, /** * 计算已过时间进度条 * @param scaleNum 已过刻度数 * @param loadData 是否加载数据 为false,则需要在调用此方法后加载数据 */ calculateProgress: function (scaleNum, loadData) { //坐标的offset可能为负数,导致scaleNum也变成了负数,但是刻度最小为0,因此在这归零 if (scaleNum < 0) scaleNum = 0; //计算总进度的百分比 var percent = scaleNum * this.scaleWidth / this.timelineWidth; var time, hours; //因为多次计算的误差,可能出现大于1或小于0的情况,需要限制最大为1,最小为0 if (percent > 1) percent = 1; else if (percent < 0) percent = 0; if (this.options.minScale === minScale.minute) { time = this.startTime + scaleNum * minuteWithMillSeconds; hours = Math.ceil(scaleNum / 60); } else { time = this.startTime + scaleNum * hourWithMillseconds; hours = scaleNum; } this.timeString = new Date(time).format('yyyy-MM-dd HH:mm'); //移除刻度标签上的样式 this.$containerElement.find('.timeline-label span').removeClass('time-passed'); // 根据结果新的刻度突出展示已过时间刻度标签 this.$containerElement.find('.timeline-label span:lt(' + hours + ')').addClass('time-passed'); if ((this.options.minScale === minScale.minute && scaleNum % 60 === 0) || this.options.minScale === minScale.hour && scaleNum !== 0) this.$containerElement.find('.timeline-label span:eq(' + hours + ')').addClass('time-passed'); //展示已过刻度 this.$containerElement.find('.timeline-line-passed').css('width', percent * 100 + '%'); if (this.lastLoadTimeString === this.timeString) { //上次加载时间为当前时间,则不加载 return; } this.showTimeTips(scaleNum); clearTimeout(this.loadDataTimer); if (loadData) { var that = this; this.loadDataTimer = setTimeout(function () { clearTimeout(that.loadDataTimer); that.options.loadData(that.timeString); that.lastLoadTimeString = that.timeString; }, this.options.loadDataDelay); } }, /** * 获取已过刻度数,用于暂停后续播。为0则表示已播放的刻度到刻度进度条的末尾了 * @returns {number} 已播放过的刻度数 */ playedScaleNum: function () { var timelineLinePassedWidth = this.$containerElement.find('.timeline-line-passed').width(); if (Math.round(timelineLinePassedWidth / this.scaleWidth) === Math.round(this.timelineLineLimitWidth / this.scaleWidth)) return 0; return Math.round(timelineLinePassedWidth / this.scaleWidth); }, /** * 开始自动播放 * @param playedScaleNum 已播放刻度数 */ startPlay: function (playedScaleNum) { playedScaleNum = playedScaleNum || 0; var scaleNum; if (this.options.minScale === minScale.minute) { scaleNum = this.scale.minutes; } else { scaleNum = this.scale.hours; } // 如果已播放的刻度数大于总刻度数或已播放刻度数大于可拖动刻度数,则停止播放 if (playedScaleNum > scaleNum || (this.options.limit && playedScaleNum > this.limitScaleNum())) { this.stopPlay(); return; } if (this.options.minScale === minScale.minute && playedScaleNum > this.scale.limitMinutes) { this.stopPlay(); return; } else if (this.options.minScale === minScale.hour && playedScaleNum > this.scale.limitHours) { this.stopPlay(); return; } this.startedAutoPlay = true; var that = this; this.calculateProgress(playedScaleNum, false); that.options.autoPlayLoadData(that.timeString); that.autoPlayTimer = setTimeout(function () { that.startPlay(playedScaleNum + 1); }, that.options.autoPlayDuration); }, /** * 停止自动播放 */ stopPlay: function () { if (this.autoPlayTimer) { clearTimeout(this.autoPlayTimer); this.togglePlay(false); } this.startedAutoPlay = false; }, /** * 销毁时间轴 */ destroy: function () { this.stopPlay(); this.$containerElement.empty(); } } $.fn.timeline = function (options) { options = $.extend(true, defaults, options); return new timeline(this, options); } })(jQuery);