<div class="timeline-container"> <div class="period" style="left: 0%; width: 20%"> <div class="period-handle left"></div> <div class="period-handle right"></div> <div class="period-time">Time</div> </div> </div> <script> const timelineContainer = document.querySelector(".timeline-container"); const periodTimeLine = document.querySelector(".period"); const t_start = {{ t_start }}; const t_end = {{ t_end }}; const tick_time = 60 / {{ tick_time }}; const tick_delay = 1 / tick_time; const period_default = {{ periode_defaut }}; let handleMoving = false; function createTicks() { let i = t_start; while (i <= t_end) { const hourTick = document.createElement("div"); hourTick.classList.add("tick", "hour"); hourTick.style.left = `${((i - t_start) / (t_end - t_start)) * 100}%`; timelineContainer.appendChild(hourTick); const tickLabel = document.createElement("div"); tickLabel.classList.add("tick-label"); tickLabel.style.left = `${((i - t_start) / (t_end - t_start)) * 100}%`; tickLabel.textContent = numberToTime(i); timelineContainer.appendChild(tickLabel); if (i < t_end) { let j = Math.floor(i + 1); while (i < j) { i += tick_delay; if (i <= t_end) { const quarterTick = document.createElement("div"); quarterTick.classList.add("tick", "quarter"); quarterTick.style.left = `${computePercentage(i, t_start)}%`; timelineContainer.appendChild(quarterTick); } } i = j; } else { i++; } } } function numberToTime(num) { const integer = Math.floor(num); const decimal = Math.round((num % 1) * 60); let dec = `:${decimal}`; if (decimal < 10) { dec = `:0${decimal}`; } let int = `${integer}`; if (integer < 10) { int = `0${integer}`; } return int + dec; } function snapToQuarter(value) { return Math.round(value * tick_time) / tick_time; } function updatePeriodTimeLabel() { const values = getPeriodValues(); const deb = numberToTime(values[0]) const fin = numberToTime(values[1]) const text = `${deb} - ${fin}` periodTimeLine.querySelector('.period-time').textContent = text; } function timelineMainEvent(event) { const startX = (event.clientX || event.changedTouches[0].clientX); if (event.target.classList.contains("period-handle")) { const startWidth = parseFloat(periodTimeLine.style.width); const startLeft = parseFloat(periodTimeLine.style.left); const isLeftHandle = event.target.classList.contains("left"); handleMoving = true const onMouseMove = (moveEvent) => { if (!handleMoving) return; const deltaX = (moveEvent.clientX || moveEvent.changedTouches[0].clientX) - startX; const containerWidth = timelineContainer.clientWidth; const newWidth = startWidth + ((isLeftHandle ? -deltaX : deltaX) / containerWidth) * 100; if (isLeftHandle) { const newLeft = startLeft + (deltaX / containerWidth) * 100; adjustPeriodPosition(newLeft, newWidth); } else { adjustPeriodPosition(parseFloat(periodTimeLine.style.left), newWidth); } updatePeriodTimeLabel(); }; const mouseUp = () => { snapHandlesToQuarters(); timelineContainer.removeEventListener("mousemove", onMouseMove); handleMoving = false; func_call(); } timelineContainer.addEventListener("mousemove", onMouseMove); timelineContainer.addEventListener("touchmove", onMouseMove); document.addEventListener( "mouseup", mouseUp, { once: true } ); document.addEventListener( "touchend", mouseUp, { once: true } ); } else if (event.target === periodTimeLine) { const startLeft = parseFloat(periodTimeLine.style.left); const onMouseMove = (moveEvent) => { if (handleMoving) return; const deltaX = (moveEvent.clientX || moveEvent.changedTouches[0].clientX) - startX; const containerWidth = timelineContainer.clientWidth; const newLeft = startLeft + (deltaX / containerWidth) * 100; adjustPeriodPosition(newLeft, parseFloat(periodTimeLine.style.width)); updatePeriodTimeLabel(); }; const mouseUp = () => { snapHandlesToQuarters(); timelineContainer.removeEventListener("mousemove", onMouseMove); func_call(); } timelineContainer.addEventListener("mousemove", onMouseMove); timelineContainer.addEventListener("touchmove", onMouseMove); document.addEventListener( "mouseup", mouseUp, { once: true } ); document.addEventListener( "touchend", mouseUp, { once: true } ); } } let func_call = () => { }; function setupTimeLine(callback) { func_call = callback; timelineContainer.addEventListener("mousedown", (e) => { timelineMainEvent(e) }); timelineContainer.addEventListener("touchstart", (e) => { timelineMainEvent(e) }); } function adjustPeriodPosition(newLeft, newWidth) { const snappedLeft = snapToQuarter(newLeft); const snappedWidth = snapToQuarter(newWidth); const minLeft = 0; const maxLeft = 100 - snappedWidth; const clampedLeft = Math.min(Math.max(snappedLeft, minLeft), maxLeft); periodTimeLine.style.left = `${clampedLeft}%`; periodTimeLine.style.width = `${snappedWidth}%`; } function getPeriodValues() { const leftPercentage = parseFloat(periodTimeLine.style.left); const widthPercentage = parseFloat(periodTimeLine.style.width); const startHour = (leftPercentage / 100) * (t_end - t_start) + t_start; const endHour = ((leftPercentage + widthPercentage) / 100) * (t_end - t_start) + t_start; const startValue = snapToQuarter(startHour); const endValue = snapToQuarter(endHour); const computedValues = [Math.max(startValue, t_start), Math.min(t_end, endValue)]; if (computedValues[0] > t_end || computedValues[1] < t_start) { return [t_start, min(t_end, t_start + period_default)]; } if (computedValues[1] - computedValues[0] <= tick_delay && computedValues[1] < t_end - tick_delay) { computedValues[1] += tick_delay; } return computedValues; } function setPeriodValues(deb, fin) { if (fin < deb) { throw new RangeError(`le paramètre 'deb' doit être inférieur au paramètre 'fin' ([${deb};${fin}])`) } if (deb < 0 || fin < 0) { throw new RangeError(`Les paramètres doivent être des entiers positifis ([${deb};${fin}])`) } deb = snapToQuarter(deb); fin = snapToQuarter(fin); let leftPercentage = (deb - t_start) / (t_end - t_start) * 100; let widthPercentage = (fin - deb) / (t_end - t_start) * 100; periodTimeLine.style.left = `${leftPercentage}%`; periodTimeLine.style.width = `${widthPercentage}%`; snapHandlesToQuarters(); updatePeriodTimeLabel() func_call(); } function snapHandlesToQuarters() { const periodValues = getPeriodValues(); let lef = Math.min(computePercentage(Math.abs(periodValues[0]), t_start), computePercentage(Math.abs(t_end), tick_delay)); if (lef < 0) { lef = 0; } const left = `${lef}%`; let wid = Math.max(computePercentage(Math.abs(periodValues[1]), Math.abs(periodValues[0])), computePercentage(tick_delay, 0)); if (wid > 100) { wid = 100; } const width = `${wid}%` periodTimeLine.style.left = left; periodTimeLine.style.width = width; updatePeriodTimeLabel() } function computePercentage(a, b) { return ((a - b) / (t_end - t_start)) * 100; } function fromTime(time, separator = ":") { const [hours, minutes] = time.split(separator).map((el) => Number(el)) return hours + minutes / 60 } createTicks(); setPeriodValues(t_start, t_start + period_default); {% if heures %} let [heure_deb, heure_fin] = [{{ heures | safe }}] if (heure_deb != '' && heure_fin != '') { heure_deb = fromTime(heure_deb); heure_fin = fromTime(heure_fin); setPeriodValues(heure_deb, heure_fin) } {% endif %} </script> <style> .timeline-container { width: 75%; margin-left: 25px; background-color: white; border-radius: 15px; position: relative; height: 40px; margin-bottom: 25px; } /* ... */ .tick { position: absolute; bottom: 0; width: 1px; background-color: rgba(0, 0, 0, 0.5); } .tick.hour { height: 100%; } .tick.quarter { height: 50%; } .tick-label { position: absolute; bottom: 0; font-size: 12px; text-align: center; transform: translateY(100%) translateX(-50%); user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; } .period { position: absolute; height: 100%; background-color: var(--color-secondary); border-radius: 15px; } .period-handle { position: absolute; top: 0; bottom: 0; width: 8px; border-radius: 0 4px 4px 0; cursor: col-resize; } .period-handle.right { right: 0; border-radius: 4px 0 0 4px; } .period .period-time { display: none; position: absolute; left: calc(50% - var(--w)/2 - 5px); justify-content: center; align-content: center; top: calc(-60% - 10px); --w: 10em; width: var(--w); } .period:hover .period-time { display: flex; background-color: var(--color-secondary); border-radius: 15px; padding: 5px; } </style>