前言
开发工作中常常有一个通过启动时间轴切换地图图层切换的功能需求,但是又很难找到一个满足要求的时间轴,所以自己撸了一个,目前还有一些功能需要改进,这里先记录一下代码
代码
/**
* AnimationSlider.vue 可以播放的timeLine
* @Author ZhangJun
* @Date 2024/2/20 16:08
**/
<template><div class="animationSliderContainer flex"><a-space :size="5" style="padding: 10px;margin: auto;" align="end" direction="vertical"><slot name="operation"><a-space><a-popover content="上一个"><a-button type="primary" @click="preTickItem()" icon="step-backward" size="small"></a-button></a-popover><a-popover content="开始/暂停"><a-button type="primary" @click="onPlay()" :icon="isStartPlay?'pause':'caret-right'"size="small"></a-button></a-popover><a-popover content="下一个"><a-button type="primary" @click="nextTickItem()" icon="step-forward" size="small"></a-button></a-popover><a-popover content="重播"><a-button type="primary" @click="onReset()" icon="redo" size="small"></a-button></a-popover></a-space></slot><slot name="currentDateTime" :dateTime="getCurrentDateTime"><div :style="{color:tickColor}">{{ getCurrentDateTime }}</div></slot></a-space><div class="tickBox" ref="tickBox" :style="{height:hourTickHeight+60+'px'}"><!--过去/未来标记--><a-space size="0" align="top" class="relative text-white" :style="{left:this.nowDateTimeMarkPosition}"style="width: 0;height:0;bottom:0;"><span style="white-space: nowrap;color: #84a4de">过去</span><a-icon type="caret-left"/><div class="divider_now"></div><a-icon type="caret-right"/><span style="white-space: nowrap;color: #92d292">未来</span></a-space><a-space :size="0" align="center"><template v-for="(item,index) in getTicks"><div :key="index"><!--刻度--><div class="tick_l"><!--里面的小刻度--><template v-for="(t,i) in item.values"><div class="flex"><a-popover :key="i"><template slot="content">{{ moment(t.dateTime, 'YYYYMMDDHHmm').format('YYYY-MM-DD HH:mm') }}</template><a-space :size="0" align="end"><div class="tick_s" :style="getActiveTickItemStyle(t)"@click="onClickTickItem(t)"></div><div class="divider_sm" v-if="item.values.length-1>i"></div></a-space></a-popover></div></template><!--上面的label--><div class="divider_lg" v-if="item.label" :style="{backgroundColor: dayTickColor}"><div class="relative":style="{left:(item.label.length/2)*(-9.5)+'px',top:'-24px',whiteSpace:'nowrap',color:tickTextColor}">{{ item.label }}</div></div><!--下面的刻度提示--><div class="divider_lg" v-else><span v-if="item.tickName" class="relative":style="{left:(item.tickName.length/2)*(-9.5)+'px',bottom:`-${hourTickHeightPix}`,whiteSpace:'nowrap',color:tickTextColor}">{{item.tickName}}</span></div></div></div></template></a-space></div></div>
</template><script>
import moment from "moment";
import {InvalidNodeTypeError} from "core-js/internals/dom-exception-constants";
import BigNumber from "bignumber.js";export default {name: "AnimationSlider",props: {//此刻的时间点nowDateTime: {type: String, default: moment().format('YYYYMMDDHHmm')},precision: {type: Number, default: 0.5},//每个单位刻度代表的小时数daysSpan: {Type: Array, default: [2, 2]},//时间段的分布(前面2天,后面2天)hourly: {type: Number, default: 3},//刻度值显示的范围距离(小时)timeout: {type: Number, default: 500},//播放间隔时间tickWidth: {type: Number, default: 30},//每个刻度的widthtickHeight: {type: Number, default: 10},//每个刻度的heighttickColor: {type: String, default: 'white'},//刻度线的颜色tickFillColor: {type: String, default: '#afb9cc'},//单位刻度填充的颜色tickBgColor: {type: String, default: '#5b5d60'},//背景的颜色tickTextColor: {type: String, default: 'white'},//文字的颜色hourTickColor: {type: String, default: 'white'},//显示小时刻度的颜色hourTickHeight: {type: Number, default: 20},//显示小时刻度的heightdayTickColor: {type: String, default: '#92d292'},//显示日期刻度的颜色activeTickColor: {type: String, default: '#6a98ea'},//显示日期刻度的颜色nowTickHeight: {type: Number, default: 50},//当前时间刻度的heightnowTickColor: {type: String, default: '#6a98ea'}///当前时间刻度的color},data() {return {moment: moment,startTick: 0,startDateTime: null,endDateTime: null,totalHours: 24,//一共展示的小时activeItems: null,//处于激活状态的tickItem集合isStartPlay: false,setIntervalHandler: null,scrollInterval: null,tickWidthValue: this.tickWidth + 'px',tickHeightValue: this.tickHeight + 'px',nowDateTimeMarkPosition: {},updateNowDateTimeMarkPositionHandler: null,nowTickHeightPix: `${this.nowTickHeight}px`,hourTickHeightPix: `${this.hourTickHeight}px`}},computed: {getTicks() {let ticks = [];let current = this.startTick;let tick_l = {values: []};//起始时间let startDateTime = moment(this.startDateTime, 'YYYYMMDDHH');while (current <= this.totalHours) {current = new BigNumber(current).plus(this.precision).toNumber();if (!tick_l?.values) {tick_l.values = [];}//单元刻度表示的时间let dateTime = startDateTime.add(this.precision, 'hour');dateTime = dateTime.format('YYYYMMDDHHmm');let tickItem = {value: current, dateTime}tick_l.values = [...tick_l.values, tickItem]//如果是整数if (Number.isInteger(current)) {// tick_l.label = '0分';ticks = [...ticks, tick_l];tick_l = {values: []};}}//如果有格式化的方法就格式化刻度的数据源ticks = this.defaultFormat(ticks);return ticks;},//得到当前选择的时间getCurrentDateTime() {if (this?.activeItems?.length > 1) {let [startItem] = this?.activeItems;let endItem = this?.activeItems[this.activeItems.length - 1];let startDateTime_temp = moment(startItem.dateTime, 'YYYYMMDDHHmm').format('YYYY年MM月DD日 HH:mm');let endDateTime_temp = moment(endItem.dateTime, 'YYYYMMDDHHmm').format('YYYY年MM月DD日 HH:mm');return `${startDateTime_temp} ~ ${endDateTime_temp}`;} else if (this?.activeItems?.length === 1) {let [{dateTime}] = this?.activeItems;if (dateTime) {return moment(dateTime, 'YYYYMMDDHHmm').format('YYYY/MM/DD HH:mm');}}return '';},},methods: {//根据逐时格式化刻度数据defaultFormat(data = []) {return data.map((item, index) => {index++;let {dateTime} = [...item.values].pop();dateTime = moment(dateTime, 'YYYYMMDDHH');//逐小时的数值if (index % this.hourly === 0) {item.tickName = dateTime.format('HH') + '时';}if (dateTime.format('HH') === '00') {item.label = dateTime.format('MM月DD日');}return item;});},//动态获取tick的样式getActiveTickItemStyle(tickItem) {let isActive = this?.activeItems?.findIndex(({dateTime}) => dateTime === tickItem?.dateTime) > -1;return isActive ? {backgroundColor: this.activeTickColor} : {};},//点击tickItem事件回调onClickTickItem(tickItem) {this.activeItems = [tickItem];},clearIntervalHandler() {if (this.setIntervalHandler) {clearInterval(this.setIntervalHandler);this.setIntervalHandler = null;}this.isStartPlay = false;this.clearAutoScroll();},//刻度添加一个单位值addTickItemsValue({dateTime, value}) {if (dateTime) {dateTime = moment(dateTime, 'YYYYMMDDHHmm').add(this.precision, 'hour').format('YYYYMMDDHHmm');return {dateTime,value: value + 1}}},//刻度减少一个单位值subtractTickItemsValue({dateTime, value}) {if (dateTime) {dateTime = moment(dateTime, 'YYYYMMDDHHmm').subtract(this.precision, 'hour').format('YYYYMMDDHHmm');return {dateTime,value: value - 1}}},//回到上一个刻度preTickItem() {let preItems = this.activeItems.map((item) => {return this.subtractTickItemsValue(item);});//上一个item超过了起始时间if (preItems?.[0]?.dateTime <= this.startDateTime) {return;}this.activeItems = preItems;},//前进下一个刻度nextTickItem() {let nextItems = this.activeItems.map((item) => {return this.addTickItemsValue(item);});if (nextItems?.[0]?.dateTime > this.endDateTime) {//默认从头开始this.activeItems = [{dateTime: this.startDateTime, value: this.startTick}];this.nextTickItem();} else {this.activeItems = nextItems;}},//重新开始onReset() {this.clearAllInterval();//默认从头开始this.activeItems = [{dateTime: this.startDateTime, value: this.startTick}];this.isStartPlay = false;let days = this.daysSpan.reduce((a, b) => a + b);//一个小时表示的widthlet hourWidth = new BigNumber(1).div(this.precision).times(this.tickWidth);//这几天tick的总的widthlet sumWidth = new BigNumber(days).times(24).times(hourWidth);this.$refs.tickBox.scrollBy(-sumWidth, 0);this.onPlay();},onPlay() {this.isStartPlay = !this.isStartPlay;if (this.isStartPlay) {this.nextTickItem();let tickBoxWidth = this?.$refs?.tickBox?.offsetWidth;this.setIntervalHandler = setInterval(() => {this.nextTickItem();if (!this.scrollInterval) {this.startAutoScroll(tickBoxWidth);}//播放到最后的时候就停止if (this?.activeItems?.[0]?.dateTime === this.endDateTime) {this.clearIntervalHandler();}}, this.timeout);} else {this.clearIntervalHandler();}},//更新当前时间标记的位置updateNowDateTimeMarkPosition() {//计算当前时间跟起点时间的时间差let durationHours = moment.duration(moment().diff(moment(this.startDateTime, 'YYYYMMDDHHmmss'))).asHours();//还要加上刻度线let tickWidth = new BigNumber(this.tickWidth).plus(1).toNumber();this.nowDateTimeMarkPosition = new BigNumber(1).div(this.precision).times(tickWidth).times(durationHours).minus(43).toString() + 'px';},//开始滚动startAutoScroll(tickBoxWidth, timeout = this.timeout) {if (this?.$refs?.tickBox) {let days = this.daysSpan.reduce((a, b) => a + b);//一个小时表示的widthlet hourWidth = new BigNumber(1).div(this.precision).times(this.tickWidth);//这几天tick的总的widthlet sumWidth = new BigNumber(days).times(24).times(hourWidth);//一共拥有多少个tickBoxlet count = new BigNumber(sumWidth).div(tickBoxWidth);//还要加上刻度线let tickWidth = new BigNumber(this.tickWidth).plus(1).toNumber();timeout = new BigNumber(timeout).times(new BigNumber(1).div(count.minus(0.5)).plus(1));this.scrollInterval = setInterval(() => {this.$refs.tickBox.scrollBy(tickWidth, 0);}, timeout)}},//停止滚动clearAutoScroll() {if (this.scrollInterval) {clearInterval(this.scrollInterval);this.scrollInterval = null;}},//开始自动更新当前时间标记startUpdateNowDateTimeMark() {this.updateNowDateTimeMarkPosition();this.updateNowDateTimeMarkPositionHandler = setInterval(() => {this.updateNowDateTimeMarkPosition();}, 1000 * 60);},//停止自动更新当前时间标记clearUpdateNowDateTimeMark() {if (this.updateNowDateTimeMarkPositionHandler) {clearInterval(this.updateNowDateTimeMarkPositionHandler);this.updateNowDateTimeMarkPositionHandler = null;}},init() {//一共有多少小时this.totalHours = this.daysSpan.reduce((a, b) => a + b) * 24;let [pre, next] = this.daysSpan;//获取开始时间this.startDateTime = pre > 0 ? moment(this.nowDateTime, 'YYYYMMDD').subtract(pre, 'days').format('YYYYMMDDHHmm') : this.nowDateTime;//获取结束时间this.endDateTime = next > 0 ? moment(this.nowDateTime, 'YYYYMMDD').add(next, 'days').format('YYYYMMDDHHmm') : this.nowDateTime;//开始自动更新this.startUpdateNowDateTimeMark();},clearAllInterval() {this.clearUpdateNowDateTimeMark();this.clearIntervalHandler();this.clearAutoScroll();},},created() {this.init();},mounted() {//默认从头开始this.activeItems = [{dateTime: this.startDateTime, value: this.startTick}];},destroyed() {this.clearAllInterval();}
}
</script><style lang="less" scoped>
@borderColor: lightblue;
@tickHeight: v-bind(tickHeightValue);
@tickWidth: v-bind(tickWidthValue);
@tickColor: v-bind(tickColor);
@hourTickColor: v-bind(hourTickColor);
@tickBgColor: v-bind(tickBgColor);
@tickFillColor: v-bind(tickFillColor);
@nowTickHeight: v-bind(nowTickHeightPix);
@nowTickColor: v-bind(nowTickColor);
@hourTickHeight: v-bind(hourTickHeightPix);.animationSliderContainer {border: 1px solid @borderColor;background-color: @tickBgColor;width: 100%;overflow: hidden;/* 滚动条样式 */::-webkit-scrollbar {width: 8px; /* 设置滚动条宽度 */height: 8px; /* 设置滚动条高度 */}::-webkit-scrollbar-thumb {background-color: #999; /* 设置滑块背景色 */border-radius: 2px; /* 设置滑块边角圆角程度 */}::-webkit-scrollbar-track {background-color: #f1f1f1; /* 设置滚动条轨道背景色 */border-radius: 0; /* 设置滚动条轨道边角圆角程度 */}::-webkit-scrollbar-button {display: none; /* 不显示按钮 */}
}.tickBox {border-left: 1px solid @borderColor;overflow: auto;padding: 5px 0;
}.tick_l {display: flex;align-items: flex-end;
}.tick_s {width: @tickWidth;height: @tickHeight;background-color: @tickFillColor;
}.divider_lg {height: @hourTickHeight;width: 1px;background-color: @hourTickColor;
}.divider_sm {height: @tickHeight;width: 1px;background-color: @tickColor;
}.divider_now {height: @nowTickHeight;width: 1px;background-color: @nowTickColor;
}.flex {display: flex;
}.w-full {width: 100%;
}.text-right {text-align: right;
}.relative {position: relative;
}.text-white {color: white;
}
</style>
参数说明
//此刻的时间点nowDateTime: {type: String, default: moment().format('YYYYMMDDHHmm')},precision: {type: Number, default: 0.5},//每个单位刻度代表的小时数daysSpan: {Type: Array, default: [2, 2]},//时间段的分布(前面2天,后面2天)hourly: {type: Number, default: 3},//刻度值显示的范围距离(小时)timeout: {type: Number, default: 500},//播放间隔时间tickWidth: {type: Number, default: 30},//每个刻度的widthtickHeight: {type: Number, default: 10},//每个刻度的heighttickColor: {type: String, default: 'white'},//刻度线的颜色tickFillColor: {type: String, default: '#afb9cc'},//单位刻度填充的颜色tickBgColor: {type: String, default: '#5b5d60'},//背景的颜色tickTextColor: {type: String, default: 'white'},//文字的颜色hourTickColor: {type: String, default: 'white'},//显示小时刻度的颜色hourTickHeight: {type: Number, default: 20},//显示小时刻度的heightdayTickColor: {type: String, default: '#92d292'},//显示日期刻度的颜色activeTickColor: {type: String, default: '#6a98ea'},//显示日期刻度的颜色nowTickHeight: {type: Number, default: 50},//当前时间刻度的heightnowTickColor: {type: String, default: '#6a98ea'}///当前时间刻度的color