Skip to content

一个基于Vue3、Vite的时间轴组件,一般用于监控视频的回放;A timeline component based on Vue3 and Vite, typically used for monitoring video playback

License

Notifications You must be signed in to change notification settings

gitboyzcf/time-line

Repository files navigation

time-line

一个基于Vue3、Vite的时间轴组件,一般用于监控视频的回放

安装

npm install @boyzcf/vue3-time-line
or
pnpm install @boyzcf/vue3-time-line
or
yarn add @boyzcf/vue3-time-line

全局引入

// main.js
import { createApp } from 'vue'
import App from './App.vue'

import TimeLine from 'time-line'

const app = createApp(App)

app.use(TimeLine, { comName: 'TimeLine' })
app.mount('#app')

局部引入

// demo.vue

import { TimeLineCom } from 'time-line'

基础用法

展开查看

<TimeLine @timeChange="timeChange"></TimeLine>

time2: '2021-01-15 00:00:00',
const timeChange = (time) => {
  defaultData.time = t
}

显示时间段

展开查看

  <template>
    <div class="container">
      <div class="timeShow">当前时间:{{ showTime2 }}</div>
      <div class="timeLine">
        <TimeLine
          ref="Timeline2"
          :initTime="defaultData.time2"
          @timeChange="timeChange2"
          :timeSegments="defaultData.timeSegments"
          @click_timeSegments="click_timeSegments"
          @click_timeline="onClickTimeLine"
          @dragTimeChange="onDragTimeChange"
        ></TimeLine>
      </div>
    </div>
  </template>

  <script setup>
    import dayjs from 'dayjs'
    import { ref, reactive, computed, onMounted, onBeforeUnmount, watch } from 'vue'
    defineOptions({ name: 'SegmentCom' })
    const defaultData = reactive({
      // 显示时间段
      time2: '2021-01-15 00:00:00',
      timeSegments: [
        {
          name: '时间段1',
          beginTime: new Date('2021-01-13 10:00:00').getTime(),
          endTime: new Date('2021-01-14 23:00:00').getTime(),
          color: '#1a94bc',
          startRatio: 0.65,
          endRatio: 0.9
        },
        {
          name: '时间段2',
          beginTime: new Date('2021-01-15 02:00:00').getTime(),
          endTime: new Date('2021-01-15 18:00:00').getTime(),
          color: '#1a94bc',
          startRatio: 0.65,
          endRatio: 0.9
        }
      ],
      timer: null
    })
    const showTime2 = computed(() => dayjs(defaultData.time2).format('YYYY-MM-DD HH:mm:ss'))
    const Timeline2 = ref(null)

    const timeChange2 = (t) => {
      defaultData.time2 = t
    }
    const click_timeSegments = (arr, ...args) => {
      console.log('onClickTimeSegments', arr, args)
      alert('点击了:' + arr[0].name)
    }
    const onClickTimeLine = (...args) => {
      console.log('onClickTimeLine', args)
    }
    const onDragTimeChange = (...args) => {
      console.log('onDragTimeChange', args)
    }
    onMounted(() => {
      defaultData.timer = setInterval(() => {
        defaultData.time2 += 1000
        Timeline2.value.setTime(defaultData.time2)
      }, 1000)
    })

    onBeforeUnmount(() => {
      clearTimeout(defaultData.timer)
    })
    </script>

    <style lang="scss" scoped>
    .container {
      width: 1200px;
      height: 100%;
      margin: 0 auto;
      display: flex;
      position: relative;
      justify-content: center;
      flex-direction: column;

      .timeLine {
        height: 50px;
      }
    }

    .timeShow {
      margin: 10px 0;
      display: flex;
      justify-content: center;
      user-select: none;
    }
  </style>

多个时间轴

当超过一个播放窗口时可以显示多个时间轴。

展开查看

<template>
  <div class="container">
    <div class="timeShow">当前时间:{{ showTime3 }}</div>
    <div class="timeline3">
      <TimeLine
        ref="Timeline3"
        :initTime="defaultData.time3"
        @timeChange="timeChange3"
        :timeSegments="defaultData.timeSegments3"
        @click_timeSegments="click_timeSegments3"
        :windowList="defaultData.windowList"
        @click_window_timeSegments="click_window_timeSegments"
      ></TimeLine>
    </div>
  </div>
</template>

<script setup>
import dayjs from 'dayjs'
import { reactive, ref, computed, onMounted, onBeforeUnmount } from 'vue'

const defaultData = reactive({
  // 多个时间轴
  time3: '2021-01-15 00:00:00',
  timeSegments3: [
    {
      name: '时间段1',
      beginTime: new Date('2021-01-13 10:00:00').getTime(),
      endTime: new Date('2021-01-14 23:00:00').getTime(),
      color: '#FA3239',
      startRatio: 0.65,
      endRatio: 0.9
    },
    {
      name: '时间段2',
      beginTime: new Date('2021-01-15 02:00:00').getTime(),
      endTime: new Date('2021-01-15 18:00:00').getTime(),
      color: '#836ABB',
      startRatio: 0.65,
      endRatio: 0.9
    }
  ],
  windowList: [
    {
      name: '窗口1',
      timeSegments: [
        {
          name: '窗口1的时间段1',
          beginTime: new Date('2021-01-13 10:00:00').getTime(),
          endTime: new Date('2021-01-14 23:00:00').getTime(),
          color: '#FA3239',
          startRatio: 0.1,
          endRatio: 0.9
        },
        {
          name: '窗口1的时间段2',
          beginTime: new Date('2021-01-12 18:00:00').getTime(),
          endTime: new Date('2021-01-13 00:00:00').getTime(),
          color: '#00AEFF',
          startRatio: 0.1,
          endRatio: 0.9
        }
      ]
    },
    {
      name: '窗口2',
      timeSegments: [
        {
          name: '窗口2的时间段1',
          beginTime: new Date('2021-01-15 02:00:00').getTime(),
          endTime: new Date('2021-01-15 18:00:00').getTime(),
          color: '#FFCC00',
          startRatio: 0.1,
          endRatio: 0.9
        }
      ]
    },
    {
      name: '窗口3'
    },
    {
      name: '窗口4'
    },
    {
      name: '窗口5'
    },
    {
      name: '窗口6'
    }
  ],
  timer: null
})

const showTime3 = computed(() => dayjs(defaultData.time3).format('YYYY-MM-DD HH:mm:ss'))
const Timeline3 = ref(null)

const timeChange3 = (t) => {
  defaultData.time3 = t
}
const click_timeSegments3 = (arr) => {
  alert('点击了:' + arr[0].name)
}
const click_window_timeSegments = (data, index, item) => {
  alert('点击了窗口时间轴的时间段:' + data[0].name)
}
onMounted(() => {
  defaultData.timer = setInterval(() => {
    defaultData.time3 += 1000
    Timeline3.value.setTime(defaultData.time3)
  }, 1000)
})

onBeforeUnmount(() => {
  clearTimeout(defaultData.timer)
})
</script>

<style lang="scss" scoped>
.container {
  width: 1200px;
  height: 100%;
  margin: 0 auto;
  display: flex;
  position: relative;
  justify-content: center;
  flex-direction: column;

  .timeline3 {
    height: 200px;
  }
}

.timeShow {
  margin: 10px 0;
  display: flex;
  justify-content: center;
  user-select: none;
}
</style>

显示自定义元素

有时候会想在时间轴上显示一些自定义的东西,比如某个时间段显示一张图片之类的,这可以通过监听某个时间点的位置来实现。

展开查看

<template>
  <div class="container">
    <div class="timeShow">当前时间:{{ showTime4 }}</div>
    <div class="timeLine4">
      <TimeLine
        ref="Timeline4Ref"
        :initTime="defaultData.time4"
        :windowList="defaultData.windowList4"
        @timeChange="timeChange4"
      ></TimeLine>
    </div>
    <i class="icon el-icon-s-flag" ref="flagIcon" style="color: #e72528"></i>
    <i class="icon el-icon-bicycle" ref="carIcon" style="color: #2196f3"></i>
  </div>
</template>

<script setup>
// 显示自定义元素
import dayjs from 'dayjs'
import { reactive, ref, computed, onMounted, onBeforeUnmount } from 'vue'

defineOptions({ name: 'CustomCom' })
const defaultData = reactive({
  time4: '2021-01-02 00:00:00',
  windowList4: [
    {
      name: '窗口1'
    },
    {
      name: '窗口2'
    },
    {
      name: '窗口3'
    },
    {
      name: '窗口4'
    },
    {
      name: '窗口5'
    },
    {
      name: '窗口6'
    }
  ],
  timer: null
})
const showTime4 = computed(() => dayjs(defaultData.time4).format('YYYY-MM-DD HH:mm:ss'))
const Timeline4Ref = ref(null)
const flagIcon = ref(null)
const carIcon = ref(null)

const timeChange4 = (t) => {
  defaultData.time4 = t
}
onMounted(() => {
  defaultData.timer = setInterval(() => {
    defaultData.time4 += 1000
    Timeline4Ref.value.setTime(defaultData.time4)
  }, 1000)
  window.addEventListener('scroll', () => {
    Timeline4Ref.value.updateWatchTime()
  })
  Timeline4Ref.value.watchTime('2021-01-01 23:30:00', (x, y) => {
    if (x === -1 || y === -1) {
      flagIcon.value.style.display = 'none'
    } else {
      flagIcon.value.style.display = 'block'
      flagIcon.value.style.left = x + 'px'
      flagIcon.value.style.top = y + 24 + 'px'
    }
  })
  Timeline4Ref.value.watchTime(
    '2021-01-02 02:30:00',
    (x, y) => {
      if (x === -1 || y === -1) {
        carIcon.value.style.display = 'none'
      } else {
        carIcon.value.style.display = 'block'
        carIcon.value.style.left = x + 'px'
        carIcon.value.style.top = y + 'px'
      }
    },
    2
  )
})

onBeforeUnmount(() => {
  clearTimeout(defaultData.timer)
})
</script>

<style lang="scss" scoped>
.container {
  width: 1200px;
  height: 100%;
  margin: 0 auto;
  display: flex;
  position: relative;
  justify-content: center;
  flex-direction: column;

  .timeLine4 {
    height: 200px;
  }
}

.timeShow {
  margin: 10px 0;
  display: flex;
  justify-content: center;
  user-select: none;
}

.icon {
  position: fixed;
  font-size: 30px;
}
</style>

API

属性

参数 说明 类型 可选值 默认值
initTime 初始时间,中点所在的时间,默认为当天0点。可以传递数字类型的时间戳或字符串类型的时间,如:2020-12-19 18:30:00 Number/String
timeRange 中间刻度所允许显示的时间范围,即当前时间的限定范围,对象类型,字段见表1-1 Object
initZoomIndex 初始的时间分辨率,数字索引,时间分辨率数组为:['半小时', '1小时', '2小时', '6小时', '12小时', '1天', '3天', '15天', '30天', '365天', '3650天'] Number 5
showCenterLine 是否显示中间的竖线 Boolean true
centerLineStyle 中间竖线的样式,对象类型,字段见表1-2 Object
textColor 日期时间文字的颜色 String rgba(151,158,167,1)
hoverTextColor 鼠标滑过显示的时间文字颜色 String rgb(194, 202, 215)
lineColor 时间刻度的颜色 String rgba(151,158,167,1)
lineHeightRatio 时间刻度高度占时间轴高度的比例,对象格式,字段见表1-3 Object
showHoverTime 鼠标滑过时是否显示实时所在的时间 Boolean true
timeSegments 要显示的时间颜色段,对象数组类型,对象字段见表1-4 Array []
backgroundColor 时间轴背景颜色 String #262626
enableZoom 是否允许鼠标滚动切换时间分辨率 Boolean true
enableDrag 是否允许拖动 Boolean true
windowList 播放窗口列表,播放窗口数量大于1的话可以配置此项,会显示和窗口对应数量的时间轴,只有一个窗口的话请直接使用基本时间轴,对象数组类型,对象字段见表1-5 Array []
baseTimeLineHeight 当显示windowList时的基础时间轴高度 Number 50
initSelectWindowTimeLineIndex 初始选中的窗口时间轴索引 Number -1
maxClickDistance(v0.1.2+) 鼠标按下和松开的距离小于该值认为是点击事件 Number 3
roundWidthTimeSegments(v0.1.6+) 绘制时间段时对计算出来的坐标进行四舍五入,可以防止相连的时间段绘制出来有间隔的问题 Boolean true
customShowTime(v0.1.7+) 自定义显示哪些时间,详细请阅读下方【属性详解1-1】 Function
hoverTimeFormat(v0.1.9+) 格式化鼠标滑过时间,函数类型,接收一个time参数,代表当前鼠标所在的时间戳,函数需要返回一个格式化后的时间字符串,默认显示的时间格式为YYYY-MM-DD HH:mm:ss Function
showDateAtZero(v0.1.9+) 0点处是否显示日期,时间轴上默认0点处显示的日期,需要显示成时间,那么就需要设为false,然后通过formatTime属性自定义格式化 Boolean true
formatTime(v0.1.9+) 格式化时间轴显示时间,默认规则是模式YYYY年月模式YYYY-MM0MM-DD、其他HH:mm,如果想自定义,比如0点还是显示时间,不显示日期,就可以通过该函数进行格式化。如果该函数返回值为空值,那么还会走内部规则。 Function
extendZOOM(v0.1.9+) 扩展ZOOM列表,详细请参考下方【属性详解1-2】 Array []

属性详解1-1 customShowTime的用法

该属性在v0.1.7+版本开始支持

当你在使用本组件的时候可能会遇到一个很常见的问题,比如容器的宽度很小,然后时间段展示的又是几天、甚至半个月的时间,那么很容易遇到时间段里面时间都挤在一起的问题,比如这个issue,其实组件内部是内置了一些判断方法,比如在3天的时间分辨率下,对应的initZoomIndex=6,对应关系可参考上方属性表格,那么就会使用3天的这个判断规则,如下:

;(date) => {
  // 每三小时显示
  return date.getHours() % 3 === 0 && date.getMinutes() === 0
}

意思是显示3的倍数的整点小时,那么当你容器宽度不够,且时间分辨率设置的比较大,那么时间就会挤在一起看不清,这时候你就可以通过customShowTime属性传入自定义的判断方法,这个方法接收两个参数:

  • date:是否要显示的时间,可以根据该时间进行判断是否要显示这个时间
  • currentZoomIndex:当前的时间分辨率,比如3天对应的就是6,对应关系可参考上方属性表格

那么如果内置的规则不满足的话,就可以自定义,比如3天的时间分辨率下我想只显示12倍数的小时,可以这么做:

<TimeLine :customShowTime="customShowTime"></TimeLine>
export default {
  methods: {
    customShowTime(date, zoomIndex) {
      if (zoomIndex === 6) {
        return date.getHours() % 12 === 0 && date.getMinutes() === 0
      }
    }
  }
}

函数返回值需要注意一下,如果要显示返回true,如果不显示返回false,如果不处理,仍旧交给内部规则,返回其他值。

属性详解1-2 extendZOOM的用法

该属性用于扩展ZOOM列表,即时间分辨率,内置了11个时间分辨率,可以参考上方表格initZoomIndex属性,如果内置的时间分辨率满足不了你,那么可以通过该属性进行扩展。

extendZOOM为数组类型,数组的每一项为:

{
    zoom: 26, // 时间分辨率,整个时间轴表示的时间范围,单位:小时
    zoomHourGrid: 0.5, //时间分辨率对应的每格小时数,即时间轴上最小格代表多少小时
    mobileZoomHourGrid: 2, // 手机模式下时间分辨率对应的每格小时数,如果不用适配手机端,可以不用设置
}

这个数组的数据会追加到内部的ZOOM数组,对应的zoomIndex往后累加即可,内部一共有11zoom,那么你追加了一项,对应的zoomIndex为11,因为是从零开始计数。

同时你需要传递customShowTime属性来自定义控制时间显示,否则会报错,因为内置的规则只有11个。

接下来看一个案例。

只显示当天的时间, 从00:00:00到23:59:59,详细请看这个issue

首先默认的initZoomIndex5,即1天,刚好满足,不用修改,然后将enableZoom设为false,不允许修改时间分辨率;将enableDrag设为false,不允许拖拽; 然后再将initTime设为当天的12:00:00,那么刚好整个时间轴显示的就是当前的时间,到这里,似乎就可以了。但是实际上会存在一些问题,如前面的issue中所示。

问题1:0点处显示的是日期,需要改成时间

很简单,将showDateAtZero设为false0点就不会显示日期了

问题2:鼠标滑过显示的还是带日期的,这个好办,通过hoverTimeFormat属性自定义格式化规则即可:

hoverTimeFormat(time) {
    // 小于今天,大于今天的时间不显示
    if (
        dayjs(time).isBefore(dayjs().format('YYYY-MM-DD 00:00:00')) ||
        dayjs(time).isAfter(dayjs().format('YYYY-MM-DD 23:59:59'))
    ) {
        return ''
    }
    return dayjs(time).format('HH:mm:ss')
}

问题3:左右两侧的时间显示不出来

0点和24点的时间刚好是两端,因实现原理问题,无法显示,怎么办呢,其实很简单,假如时间轴表示的时间范围为25小时,那么左右两端不就会各多出半小时的时间吗,这个空间足够显示时间了,但是内部的时间分辨率没有25小时的,这时就需要扩展时间分辨率了:

extendZOOM: [
  {
    zoom: 25,
    zoomHourGrid: 0.5
  }
]

扩展了extendZOOMcustomShowTime不能少,否则会报错:

customShowTime(date, zoomIndex) {
    // 当zoomIndex等于11,也就是等于我们开展的zoom时才自己处理
    if (zoomIndex === 11) {
        // 时间是2的倍数时才会显示
        return date.getHours() % 2 === 0 && date.getMinutes() === 0
    }
}

到这里,你还会发现一个问题,24点实际上是下一天的0点,所以显示的是00:00,这样可能不符合我们的需求,这时我们可以通过formatTime来格式化时间轴上的时间显示,判断是下一天的0点,那么就改成24:00

formatTime(time) {
    // 下一天的00:00显示24:00
    if (time.isAfter(dayjs().format('YYYY-MM-DD 23:59:59'))) {
        return '24:00'
    }
    if (
        time.hour() === 0 &&
        time.minute() === 0 &&
        time.millisecond() === 0
    ) {
        return time.format('HH:mm')
    }
}

完整代码请参考:CustomZoom.vue

表1-1 timeRange对象的字段格式

字段名 说明 类型 可选值 默认值
start 允许显示的最小时间,可以传递数字类型的时间戳或字符串类型的时间,如:2020-12-19 18:30:00 Number/String
end 允许显示的最大时间,可以传递数字类型的时间戳或字符串类型的时间,如:2020-12-19 18:30:00 Number/String

表1-2 centerLineStyle对象的字段格式

字段名 说明 类型 可选值 默认值
width 线的宽度,单位px Number 2
color 线的颜色 String #fff

表1-3 lineHeightRatio对象的字段格式

字段名 说明 类型 可选值 默认值
date 0点时的日期线段高度 Number 0.3
time 显示时间的线段高度 Number 0.2
none 不显示时间的线段高度 Number 0.1
hover 鼠标滑过时显示的时间段高度 Number 0.3

表1-4 timeSegments数组的对象元素的字段格式

字段名 说明 类型 可选值 默认值
beginTime 起始时间戳,必填 Number
endTime 结束时间戳,必填 Number
color 颜色,必填 String
startRatio 高度的起始比例,即top=时间轴高度*startRatio Number 0.6
endRatio 高度的结束比例,即bottom=时间轴高度*endRatio Number 0.9

从v0.1.8+版本开始,时间段可以只传一个beginTime,绘制一根宽度为1px的线段

表1-5 windowList数组的对象元素的字段格式

字段名 说明 类型 可选值 默认值
timeSegments 要显示的时间段,对象数组,对象字段见表1-4 Array []

可以添加你需要的其他任意字段

事件

事件 说明 回调函数参数
timeChange 当前时间切换事件 currentTime(当前时间,时间戳格式)
dragTimeChange 拖动时间条结束后的事件 currentTime(当前时间,时间戳格式)
mousedown 鼠标按下事件 e(事件对象)
mouseup 鼠标松开事件 e(事件对象)
click_timeSegments 点击到了基础时间轴里的时间段时触发 timeSegments(点击到的时间段,数组类型)、time(v0.1.10+,点击位置对应的时间戳)、 date(v0.1.10+,点击位置对应的日期时间字符串)、 x(v0.1.10+,点击位置相对时间轴左侧的距离)
click_window_timeSegments 点击到了窗口时间轴里的时间段时触发 timeSegments(点击到的时间段,数组类型)、index(时间轴索引)、item(时间轴数据)
change_window_time_line 点击窗口时间轴进行切换选中时触发 index(时间轴索引)、item(时间轴数据)
click_timeline(v0.1.2+) 时间轴的点击事件 time(点击位置对应的时间戳)、 date(点击位置对应的日期时间字符串)、 x(点击位置相对时间轴左侧的距离)

方法

方法名 说明 参数
updateWatchTime 手动更新观察的时间位置,比如页面滚动后时间轴的整体位置变化了需要调用,如果没有显示自定义元素时无需调用
reRender 重新渲染
setTime 设置当前时间 t(数字类型的时间戳或字符串类型的时间,如:2020-12-19 18:30:00)
setZoom 设置分辨率 index(分辨率索引)
watchTime 设置要观察的时间点,会返回该时间点的实时位置,可以根据该位置来设置一些自定义元素的位置,位置为相对于浏览器可视窗口的位置 time(要观察的时间,数字类型的时间戳或字符串类型的时间,如:2020-12-19 18:30:00), callback(时间点位置变化时会调用,回调参数为x水平位置、y重置位置,单位px), windowTimeLineIndex(如果自定义元素是要显示到某个窗口时间轴里的话,可以通过该参数来指定第几个时间轴,数字索引,从1开始)
onResize 如果时间轴所在的容器尺寸变化了需要调用该方法来适应

About

一个基于Vue3、Vite的时间轴组件,一般用于监控视频的回放;A timeline component based on Vue3 and Vite, typically used for monitoring video playback

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published