场景:需要对某一系列用户的操作进行时间点记录和时间轴展示
参考资料:时间轴网址 —> 对应Git地址
需要用到的JS和CSS:
插件自带功能与使用场景有些许不符,需要改造
1.新增自定义宽度属性:
2.显示问题修改:
3.修改部分自己业务需要的css样式,不在这里展开。
详细使用过程
1.定义Html元素(空DIV实体):
<div class="regionForTimeAxios"style="height:95%;"> <div id="report_Time_Axios" class="timeline"> <div class="timeline__wrap" style="height:100%;width: 100%; overflow-x: auto"> <div class="timeline__items"> </div> </div> </div> </div>
复制
2.构造时间点Html串,并塞入页面中:
var timeLineHtml = ''; //时间轴列表数据构造Html $.each(dataList, function (io, vo) { timeLineHtml += '<div class="timeline__item"><div class="timeline__content"><div class="reportTimeLineToolTip" style="width:170px;padding:2.5px 0;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;"><span>【' + vo.on + '】</span><span>' + convertOpType(vo) + '</span></div><span>' + new Date(vo.time).format('yyyy-MM-dd hh:mm:ss') + '</span></div></div > ' }); if (timeLineHtml) { //塞入元素 $('#report_Time_Axios .timeline__wrap .timeline__items').append(timeLineHtml) //时间轴渲染 $('#report_Time_Axios').timeline({ mode: 'horizontal', itemDefaultWidth: 200, //更多属性可查看JS }); }
复制
3.页面效果:
(看起来是左右滑动,实际上中间的轴并没有动)
如果设计到数据切换,渲染新数据前直接清空Div里的内容重新渲染即可。
修改后的JS和CSS文件
// https://squarechip.github.io/timeline/ 来源地址 function timeline(collection, options) { const timelines = []; const warningLabel = 'Timeline:'; let winWidth = window.innerWidth; let resizeTimer; let currentIndex = 0; // Set default settings const defaultSettings = { forceVerticalMode: { type: 'integer', defaultValue: 600 }, horizontalStartPosition: { type: 'string', acceptedValues: ['bottom', 'top'], defaultValue: 'top' }, mode: { type: 'string', acceptedValues: ['horizontal', 'vertical'], defaultValue: 'vertical' }, moveItems: { type: 'integer', defaultValue: 1 }, rtlMode: { type: 'boolean', acceptedValues: [true, false], defaultValue: false }, startIndex: { type: 'integer', defaultValue: 0 }, verticalStartPosition: { type: 'string', acceptedValues: ['left', 'right'], defaultValue: 'left' }, verticalTrigger: { type: 'string', defaultValue: '15%' }, visibleItems: { type: 'integer', defaultValue: 3 }, itemDefaultWidth: { type: 'integer', defaultValue: 0 } }; // Helper function to test whether values are an integer function testValues(value, settingName) { if (typeof value !== 'number' && value % 1 !== 0) { console.warn(`${warningLabel} The value "${value}" entered for the setting "${settingName}" is not an integer.`); return false; } return true; } // Helper function to wrap an element in another HTML element function itemWrap(el, wrapper, classes) { wrapper.classList.add(classes); el.parentNode.insertBefore(wrapper, el); wrapper.appendChild(el); } // Helper function to wrap each element in a group with other HTML elements function wrapElements(items) { items.forEach((item) => { itemWrap(item.querySelector('.timeline__content'), document.createElement('div'), 'timeline__content__wrap'); itemWrap(item.querySelector('.timeline__content__wrap'), document.createElement('div'), 'timeline__item__inner'); }); } // Helper function to check if an element is partially in the viewport function isElementInViewport(el, triggerPosition) { const rect = el.getBoundingClientRect(); const windowHeight = window.innerHeight || document.documentElement.clientHeight; const defaultTrigger = defaultSettings.verticalTrigger.defaultValue.match(/(\d*\.?\d*)(.*)/); let triggerUnit = triggerPosition.unit; let triggerValue = triggerPosition.value; let trigger = windowHeight; if (triggerUnit === 'px' && triggerValue >= windowHeight) { console.warn('The value entered for the setting "verticalTrigger" is larger than the window height. The default value will be used instead.'); [, triggerValue, triggerUnit] = defaultTrigger; } if (triggerUnit === 'px') { trigger = parseInt(trigger - triggerValue, 10); } else if (triggerUnit === '%') { trigger = parseInt(trigger * ((100 - triggerValue) / 100), 10); } return ( rect.top <= trigger && rect.left <= (window.innerWidth || document.documentElement.clientWidth) && (rect.top + rect.height) >= 0 && (rect.left + rect.width) >= 0 ); } // Helper function to add transform styles function addTransforms(el, transform) { el.style.webkitTransform = transform; el.style.msTransform = transform; el.style.transform = transform; } // Create timelines function createTimelines(timelineEl) { const timelineName = timelineEl.id ? `#${timelineEl.id}` : `.${timelineEl.className}`; const errorPart = 'could not be found as a direct descendant of'; const data = timelineEl.dataset; let wrap; let scroller; let items; const settings = {}; // Test for correct HTML structure try { wrap = timelineEl.querySelector('.timeline__wrap'); if (!wrap) { throw new Error(`${warningLabel} .timeline__wrap ${errorPart} ${timelineName}`); } else { scroller = wrap.querySelector('.timeline__items'); if (!scroller) { throw new Error(`${warningLabel} .timeline__items ${errorPart} .timeline__wrap`); } else { items = [].slice.call(scroller.children, 0); } } } catch (e) { console.warn(e.message); return false; } // Test setting input values Object.keys(defaultSettings).forEach((key) => { settings[key] = defaultSettings[key].defaultValue; if (data[key]) { settings[key] = data[key]; } else if (options && options[key]) { settings[key] = options[key]; } if (defaultSettings[key].type === 'integer') { if (!settings[key] || !testValues(settings[key], key)) { settings[key] = defaultSettings[key].defaultValue; } } else if (defaultSettings[key].type === 'string') { if (defaultSettings[key].acceptedValues && defaultSettings[key].acceptedValues.indexOf(settings[key]) === -1) { console.warn(`${warningLabel} The value "${settings[key]}" entered for the setting "${key}" was not recognised.`); settings[key] = defaultSettings[key].defaultValue; } } }); // Further specific testing of input values const defaultTrigger = defaultSettings.verticalTrigger.defaultValue.match(/(\d*\.?\d*)(.*)/); const triggerArray = settings.verticalTrigger.match(/(\d*\.?\d*)(.*)/); let [, triggerValue, triggerUnit] = triggerArray; let triggerValid = true; if (!triggerValue) { console.warn(`${warningLabel} No numercial value entered for the 'verticalTrigger' setting.`); triggerValid = false; } if (triggerUnit !== 'px' && triggerUnit !== '%') { console.warn(`${warningLabel} The setting 'verticalTrigger' must be a percentage or pixel value.`); triggerValid = false; } if (triggerUnit === '%' && (triggerValue > 100 || triggerValue < 0)) { console.warn(`${warningLabel} The 'verticalTrigger' setting value must be between 0 and 100 if using a percentage value.`); triggerValid = false; } else if (triggerUnit === 'px' && triggerValue < 0) { console.warn(`${warningLabel} The 'verticalTrigger' setting value must be above 0 if using a pixel value.`); triggerValid = false; } if (triggerValid === false) { [, triggerValue, triggerUnit] = defaultTrigger; } settings.verticalTrigger = { unit: triggerUnit, value: triggerValue }; if (settings.moveItems > settings.visibleItems) { console.warn(`${warningLabel} The value of "moveItems" (${settings.moveItems}) is larger than the number of "visibleItems" (${settings.visibleItems}). The value of "visibleItems" has been used instead.`); settings.moveItems = settings.visibleItems; } if (settings.startIndex > (items.length - settings.visibleItems) && items.length > settings.visibleItems) { console.warn(`${warningLabel} The 'startIndex' setting must be between 0 and ${items.length - settings.visibleItems} for this timeline. The value of ${items.length - settings.visibleItems} has been used instead.`); settings.startIndex = items.length - settings.visibleItems; } else if (items.length <= settings.visibleItems) { console.warn(`${warningLabel} The number of items in the timeline must exceed the number of visible items to use the 'startIndex' option.`); settings.startIndex = 0; } else if (settings.startIndex < 0) { console.warn(`${warningLabel} The 'startIndex' setting must be between 0 and ${items.length - settings.visibleItems} for this timeline. The value of 0 has been used instead.`); settings.startIndex = 0; } timelines.push({ timelineEl, wrap, scroller, items, settings }); } if (collection.length) { [].forEach.call(collection, createTimelines); } // Set height and widths of timeline elements and viewport function setHeightandWidths(tl) { // Set widths of items and viewport function setWidths() { tl.itemWidth = tl.settings.itemDefaultWidth > 0 ? tl.settings.itemDefaultWidth: tl.wrap.offsetWidth / tl.settings.visibleItems; tl.items.forEach((item) => { item.style.width = `${tl.itemWidth}px`; }); tl.scrollerWidth = tl.itemWidth * tl.items.length; tl.scroller.style.width = `${tl.scrollerWidth}px`; } // Set height of items and viewport function setHeights() { let oddIndexTallest = 0; let evenIndexTallest = 0; tl.items.forEach((item, i) => { item.style.height = 'auto'; const height = item.offsetHeight; if (i % 2 === 0) { evenIndexTallest = height > evenIndexTallest ? height : evenIndexTallest; } else { oddIndexTallest = height > oddIndexTallest ? height : oddIndexTallest; } }); const transformString = `translateY(${evenIndexTallest}px)`; tl.items.forEach((item, i) => { if (i % 2 === 0) { item.style.height = `${evenIndexTallest}px`; if (tl.settings.horizontalStartPosition === 'bottom') { item.classList.add('timeline__item--bottom'); addTransforms(item, transformString); } else { item.classList.add('timeline__item--top'); } } else { item.style.height = `${oddIndexTallest}px`; if (tl.settings.horizontalStartPosition !== 'bottom') { item.classList.add('timeline__item--bottom'); addTransforms(item, transformString); } else { item.classList.add('timeline__item--top'); } } }); if (tl.items.length == 1) { tl.scroller.style.height = `${evenIndexTallest * 2}px`; } else { tl.scroller.style.height = `${evenIndexTallest + oddIndexTallest}px`; } } if (window.innerWidth > tl.settings.forceVerticalMode) { setWidths(); setHeights(); } } // Create and add arrow controls to horizontal timeline function addNavigation(tl) { if (tl.items.length > tl.settings.visibleItems) { const prevArrow = document.createElement('button'); const nextArrow = document.createElement('button'); const topPosition = tl.items[0].offsetHeight; prevArrow.className = 'timeline-nav-button timeline-nav-button--prev'; nextArrow.className = 'timeline-nav-button timeline-nav-button--next'; prevArrow.textContent = 'Previous'; nextArrow.textContent = 'Next'; prevArrow.style.top = `${topPosition}px`; nextArrow.style.top = `${topPosition}px`; if (currentIndex === 0) { prevArrow.disabled = true; } else if (currentIndex === (tl.items.length - tl.settings.visibleItems)) { nextArrow.disabled = true; } //tl.timelineEl.appendChild(prevArrow); //tl.timelineEl.appendChild(nextArrow); } } // Add the centre line to the horizontal timeline function addHorizontalDivider(tl) { const divider = tl.timelineEl.querySelector('.timeline-divider'); if (divider) { tl.timelineEl.removeChild(divider); } const topPosition = tl.items[0].offsetHeight; const horizontalDivider = document.createElement('span'); horizontalDivider.className = 'timeline-divider'; horizontalDivider.style.top = `${topPosition}px`; tl.timelineEl.appendChild(horizontalDivider); } // Calculate the new position of the horizontal timeline function timelinePosition(tl) { const position = tl.items[currentIndex].offsetLeft; const str = `translate3d(-${position}px, 0, 0)`; addTransforms(tl.scroller, str); } // Make the horizontal timeline slide function slideTimeline(tl) { const navArrows = tl.timelineEl.querySelectorAll('.timeline-nav-button'); const arrowPrev = tl.timelineEl.querySelector('.timeline-nav-button--prev'); const arrowNext = tl.timelineEl.querySelector('.timeline-nav-button--next'); const maxIndex = tl.items.length - tl.settings.visibleItems; const moveItems = parseInt(tl.settings.moveItems, 10); [].forEach.call(navArrows, (arrow) => { arrow.addEventListener('click', function(e) { e.preventDefault(); currentIndex = this.classList.contains('timeline-nav-button--next') ? (currentIndex += moveItems) : (currentIndex -= moveItems); if (currentIndex === 0 || currentIndex < 0) { currentIndex = 0; arrowPrev.disabled = true; arrowNext.disabled = false; } else if (currentIndex === maxIndex || currentIndex > maxIndex) { currentIndex = maxIndex; arrowPrev.disabled = false; arrowNext.disabled = true; } else { arrowPrev.disabled = false; arrowNext.disabled = false; } timelinePosition(tl); }); }); } // Set up horizontal timeline function setUpHorinzontalTimeline(tl) { if (tl.settings.rtlMode) { currentIndex = tl.items.length > tl.settings.visibleItems ? tl.items.length - tl.settings.visibleItems : 0; } else { currentIndex = tl.settings.startIndex; } tl.timelineEl.classList.add('timeline--horizontal'); setHeightandWidths(tl); timelinePosition(tl); addNavigation(tl); addHorizontalDivider(tl); slideTimeline(tl); } // Set up vertical timeline function setUpVerticalTimeline(tl) { let lastVisibleIndex = 0; tl.items.forEach((item, i) => { item.classList.remove('animated', 'fadeIn'); if (!isElementInViewport(item, tl.settings.verticalTrigger) && i > 0) { item.classList.add('animated'); } else { lastVisibleIndex = i; } const divider = tl.settings.verticalStartPosition === 'left' ? 1 : 0; if (i % 2 === divider && window.innerWidth > tl.settings.forceVerticalMode) { item.classList.add('timeline__item--right'); } else { item.classList.add('timeline__item--left'); } }); for (let i = 0; i < lastVisibleIndex; i += 1) { tl.items[i].classList.remove('animated', 'fadeIn'); } // Bring elements into view as the page is scrolled window.addEventListener('scroll', () => { tl.items.forEach((item) => { if (isElementInViewport(item, tl.settings.verticalTrigger)) { item.classList.add('fadeIn'); } }); }); } // Reset timelines function resetTimelines(tl) { tl.timelineEl.classList.remove('timeline--horizontal', 'timeline--mobile'); tl.scroller.removeAttribute('style'); tl.items.forEach((item) => { item.removeAttribute('style'); item.classList.remove('animated', 'fadeIn', 'timeline__item--left', 'timeline__item--right'); }); const navArrows = tl.timelineEl.querySelectorAll('.timeline-nav-button'); [].forEach.call(navArrows, (arrow) => { arrow.parentNode.removeChild(arrow); }); } // Set up the timelines function setUpTimelines() { timelines.forEach((tl) => { tl.timelineEl.style.opacity = 0; if (!tl.timelineEl.classList.contains('timeline--loaded')) { wrapElements(tl.items); } resetTimelines(tl); if (window.innerWidth <= tl.settings.forceVerticalMode) { tl.timelineEl.classList.add('timeline--mobile'); } if (tl.settings.mode === 'horizontal' && window.innerWidth > tl.settings.forceVerticalMode) { setUpHorinzontalTimeline(tl); } else { setUpVerticalTimeline(tl); } tl.timelineEl.classList.add('timeline--loaded'); setTimeout(() => { tl.timelineEl.style.opacity = 1; }, 500); }); } // Initialise the timelines on the page setUpTimelines(); window.addEventListener('resize', () => { clearTimeout(resizeTimer); resizeTimer = setTimeout(() => { const newWinWidth = window.innerWidth; if (newWinWidth !== winWidth) { setUpTimelines(); winWidth = newWinWidth; } }, 250); }); } // Register as a jQuery plugin if the jQuery library is present if (window.jQuery) { (($) => { $.fn.timeline = function(opts) { timeline(this, opts); return this; }; })(window.jQuery); }
复制
--------------------------------------------
.timeline { -webkit-box-sizing: border-box; box-sizing: border-box; position: relative } .timeline *, .timeline :after, .timeline :before { -webkit-box-sizing: inherit; box-sizing: inherit } .timeline:not(.timeline--horizontal):before { background-color: #ddd; bottom: 0; content: ''; left: 50%; margin-left: -2px; position: absolute; top: 0; width: 4px; z-index: 1 } .timeline__wrap { overflow: hidden; position: relative; z-index: 2 } .timeline__item { font-size: 16px; font-size: 1rem; padding: .625rem 2.5rem .625rem 0; position: relative; width: 50%; z-index: 2 } .timeline__item:after { background-color: #fff; border: 2px solid #000; border-radius: 50%; content: ''; height: 10px; position: absolute; right: -10px; -webkit-transform: translateY(-50%); -ms-transform: translateY(-50%); transform: translateY(-50%); top: 50%; width: 10px; z-index: 1 } .timeline__item.animated { -webkit-animation-duration: 1s; animation-duration: 1s; -webkit-animation-fill-mode: both; animation-fill-mode: both; opacity: 0 } .timeline__item.fadeIn { -webkit-animation-name: fadeIn; animation-name: fadeIn } .timeline__item--left { left: 0 } .timeline__item--right { left: 50%; padding: .625rem 0 .625rem 2.5rem } .timeline__item--right:after { left: -10px } .timeline__item--right .timeline__content:before { border-bottom: 10px solid transparent; border-right: 12px solid #ccc; border-left: none; border-top: 10px solid transparent; left: -12px } .timeline__item--right .timeline__content:after { border-bottom: 9px solid transparent; border-right: 11px solid #fff; border-left: none; border-top: 9px solid transparent; left: -10px } .timeline__content { background-color: #fff; border: 1px solid #ccc; border-radius: 10px; color: #333; display: block; padding: 10px; position: relative; } .timeline__content:after, .timeline__content:before { content: ''; height: 0; position: absolute; -webkit-transform: translateY(-50%); -ms-transform: translateY(-50%); transform: translateY(-50%); top: 50%; width: 0 } .timeline__content:before { border-bottom: 10px solid transparent; border-left: 12px solid #ccc; border-top: 10px solid transparent; right: -12px; z-index: 1 } .timeline__content:after { border-bottom: 9px solid transparent; border-left: 11px solid #fff; border-top: 9px solid transparent; right: -10px; z-index: 2 } .timeline__content h2 { font-size: 1.25rem; font-weight: 700; margin: 0 0 .625rem } .timeline__content p { font-size: .9375rem; line-height: 1.5; margin-bottom: 10px } .timeline--horizontal { font-size: 0; padding: 0; overflow: hidden; white-space: nowrap } .timeline--horizontal .timeline-divider { background-color: #ddd; display: block; height: 4px; left: 0px; position: absolute; -webkit-transform: translateY(-50%); -ms-transform: translateY(-50%); transform: translateY(-50%); right: 0px; z-index: 1 } .timeline--horizontal .timeline__items { -webkit-transition: all .8s; -o-transition: all .8s; transition: all .8s; will-change: transform } .timeline--horizontal .timeline__item { display: inline-block; left: 0; padding: 1rem 0 1.5rem; position: relative; -webkit-transition: none; -o-transition: none; transition: none; vertical-align: top; white-space: normal } .timeline--horizontal .timeline__item:after { left: 50%; right: auto; -webkit-transform: translate(-50%,-50%); -ms-transform: translate(-50%,-50%); transform: translate(-50%,-50%); top: 100% } .timeline--horizontal .timeline__item .timeline__item__inner { display: table; height: 100%; width: 100% } .timeline--horizontal .timeline__item .timeline__content__wrap { display: table-cell; margin: 0; padding: 0 5px; vertical-align: bottom } .timeline--horizontal .timeline__item .timeline__content:before { border-left: 12px solid transparent; border-right: 12px solid transparent; border-top: 12px solid #ccc; left: 50%; right: auto; -webkit-transform: translateX(-50%); -ms-transform: translateX(-50%); transform: translateX(-50%); top: 100% } .timeline--horizontal .timeline__item .timeline__content:after { border-left: 10px solid transparent; border-right: 10px solid transparent; border-top: 10px solid #fff; left: 50%; right: auto; -webkit-transform: translateX(-50%); -ms-transform: translateX(-50%); transform: translateX(-50%); top: 100% } .timeline--horizontal .timeline__item--bottom { padding: 1.5rem 0 1rem } .timeline--horizontal .timeline__item--bottom:after { top: 0 } .timeline--horizontal .timeline__item--bottom .timeline__content__wrap { vertical-align: top } .timeline--horizontal .timeline__item--bottom .timeline__content:before { border-bottom: 12px solid #ccc; border-left: 12px solid transparent; border-right: 12px solid transparent; border-top: none; bottom: 100%; top: auto } .timeline--horizontal .timeline__item--bottom .timeline__content:after { border-bottom: 10px solid #fff; border-left: 10px solid transparent; border-right: 10px solid transparent; border-top: none; bottom: 100%; top: auto } .timeline-nav-button { background-color: #fff; border: 2px solid #ddd; border-radius: 50px; -webkit-box-sizing: border-box; box-sizing: border-box; -webkit-box-shadow: none; box-shadow: none; cursor: pointer; display: block; height: 40px; outline: 0; position: absolute; text-indent: -9999px; -webkit-transform: translateY(-50%); -ms-transform: translateY(-50%); transform: translateY(-50%); top: 50%; width: 40px; z-index: 10 } .timeline-nav-button:disabled { opacity: .5; pointer-events: none } .timeline-nav-button:before { background-position: center center; background-repeat: no-repeat; content: ''; display: block; height: 14px; left: 50%; position: absolute; -webkit-transform: translateX(-50%) translateY(-50%); -ms-transform: translateX(-50%) translateY(-50%); transform: translateX(-50%) translateY(-50%); top: 50%; width: 8px } .timeline-nav-button--prev { left: 0 } .timeline-nav-button--prev:before { background-image: url(../images/arrow-left.svg) } .timeline-nav-button--next { right: 0 } .timeline-nav-button--next:before { background-image: url(../images/arrow-right.svg) } .timeline--mobile { padding: 0 } .timeline--mobile:before { left: 10px !important; margin: 0 !important } .timeline--mobile .timeline__item { left: 0; padding-left: 40px; padding-right: 0; width: 100% } .timeline--mobile .timeline__item:after { left: 2px; margin: 0 } .timeline--mobile .timeline__item .timeline__content:before { left: -12px; border-bottom: 12px solid transparent; border-right: 12px solid #ccc; border-left: none; border-top: 12px solid transparent } .timeline--mobile .timeline__item .timeline__content:after { left: -10px; border-bottom: 10px solid transparent; border-right: 10px solid #fff; border-left: none; border-top: 10px solid transparent } @-webkit-keyframes fadeIn { 0% { opacity: 0; top: 70px } 100% { opacity: 1; top: 0 } } @keyframes fadeIn { 0% { opacity: 0; top: 70px } 100% { opacity: 1; top: 0 } } @-webkit-keyframes liftUp { 0% { top: 0 } 100% { top: -15px } } @keyframes liftUp { 0% { top: 0 } 100% { top: -15px } }
复制