/* Melpomene CSS */ /* CC-BY-NC-SA : https://git.aribaud.net/caribaud/melpomene/ */ //============ // CONTROLS //============ MOVE_NEXT = "ArrowRight" MOVE_BACK = "ArrowLeft" TOGGLE_FULLSCREEN = "F" TOGGLE_PROGRESSBAR = "P" TOGGLE_VIEW_MODE = "V" //======================== // NAVIGATION CONSTANTS //======================== PAGE_TRANSITION_SPEED = "1.5s" MOUSEWHELL_MIN_DELAY = 50 DELAY_BEFORE_HIDDING_CONTROLS = 4000; //==================== // STATES CONSTANTS //==================== READER_FRAME = document.getElementById("reader-frame") READER_CONTENT_FRAME = document.getElementById("reader-content-frame") READER_PAGES = document.getElementById("reader-pages") FOCUS_OVERLAY_HEIGHT = document.getElementById("focus-overlay-height") FOCUS_OVERLAY_WIDTH = document.getElementById("focus-overlay-width") HELP_CONTROLS = document.getElementById("help-controls") PROGRESS_BAR_CONTAINER = document.getElementById("reader-progress-container") PROGRESS_BAR = document.getElementById("reader-progress-bar") PROGRESS_BAR_PAGES = document.getElementById("reader-progress-pages") CURRENT_ZOOM = 0 CURRENT_PAGE = 1 CURRENT_WIDTH = 0 CURRENT_HEIGHT = 0 CURRENT_X = 0 CURRENT_Y = 0 IS_PAGE_MODE = false MOUSEWHELL_WAIT = false // ============= // UTILITIES // ============= // Zooms utilites // -------------- function getFirstZoomOfPage(pageNumber){ for (var zoom_idx = 0; zoom_idx < zooms.length; zoom_idx++){ if (zooms[zoom_idx][0] == pageNumber) { return zoom_idx } } } function getLastZoomOfPage(pageNumber){ let res = null for (var zoom_idx = 0; zoom_idx < zooms.length; zoom_idx++){ if (zooms[zoom_idx][0] == pageNumber) { res = zoom_idx } if (res != null && zooms[zoom_idx][0] != pageNumber) { break } } return res } function getZoomCountForPage(pageNumber) { return zooms.filter(zoom => zoom[0] == pageNumber).length } function getCurrentZoomIndexForPage() { previousZoomsCount = zooms.filter(zoom => zoom[0] < CURRENT_PAGE).length return CURRENT_ZOOM - previousZoomsCount + 1 } function getReadingProgressPercent() { progressPerPage = 1 / getPagesCount() if (IS_PAGE_MODE){ return 100 * progressPerPage * CURRENT_PAGE } progressPerZoom = progressPerPage / getZoomCountForPage(CURRENT_PAGE) readingProgress = (CURRENT_PAGE - 1) * progressPerPage + getCurrentZoomIndexForPage() * progressPerZoom return 100 * readingProgress } function updateProgressBar(){ PROGRESS_BAR.style.width = getReadingProgressPercent() + "%" } // Dimensions utilites // ------------------- function getPagesCount() { return READER_PAGES.childElementCount } function pageOriginalHeight() { return parseInt(READER_PAGES.dataset.pagesHeight) } function pageOriginalWidth() { return parseInt(READER_PAGES.dataset.pagesWidth) } function readerFrameRatio() { return READER_CONTENT_FRAME.clientWidth / READER_CONTENT_FRAME.clientHeight } function pageRatio() { return READER_PAGES.dataset.pagesWidth / READER_PAGES.dataset.pagesHeight } function isFrameRatioWiderThanPage(){ return readerFrameRatio() > pageRatio() } function pageToFrameScaleFactor(useHeight){ // The scale factor to apply to a page so it exactly fit in the reader frame if (useHeight) { return READER_CONTENT_FRAME.clientHeight / pageOriginalHeight() } return READER_CONTENT_FRAME.clientWidth / pageOriginalWidth() } function totalPagesWidth() { // The width of all cumuled pages with scale factor applied return pageOriginalWidth() * getPagesCount() } // ========= // ACTIONS // ========= function initReader(){ moveReaderDisplayToZoom(0) // Smoothly show pictures when they intersect with the viewport let visibilityObserver = new IntersectionObserver((entries, observer) => { entries.forEach((entry) => { if (entry.isIntersecting) { entry.target.style.opacity = 1 entry.target.style.visibility = "visible" } else { entry.target.style.opacity = 0 entry.target.style.visibility = "hidden" } }); }, { root: READER_CONTENT_FRAME, rootMargin: "-10px" }); for (var i = 0; i < READER_PAGES.children.length; i++) { let img = READER_PAGES.children[i]; img.style.width = 100 / getPagesCount() + "%" visibilityObserver.observe(img) PROGRESS_BAR_PAGES.appendChild(document.createElement("div")) } READER_PAGES.style.display = "flex" setTimeout(() => { READER_PAGES.hidden = false }, "300") setTimeout(() => { HELP_CONTROLS.style.opacity = null; HELP_CONTROLS.style.transform = null; }, DELAY_BEFORE_HIDDING_CONTROLS) } function moveReaderDisplayToArea(pageNumber, width, height, posx, posy){ if (width == 0){ width = pageOriginalWidth() } if (height == 0){ height = pageOriginalHeight() } zoomRatio = width / height if (readerFrameRatio() > zoomRatio) { // Frame wider than zoom var zoomToFrameScaleFactor = pageToFrameScaleFactor(true) * pageOriginalHeight() / height var zoomToFrameCenteringOffset = [(READER_CONTENT_FRAME.clientWidth - width * zoomToFrameScaleFactor) / 2, 0] updateFocusByWidth(width * zoomToFrameScaleFactor) } else { var zoomToFrameScaleFactor = pageToFrameScaleFactor(false) * pageOriginalWidth() / width var zoomToFrameCenteringOffset = [0, (READER_CONTENT_FRAME.clientHeight - height * zoomToFrameScaleFactor) / 2] updateFocusByHeight(height * zoomToFrameScaleFactor) } previousPagesOffset = pageOriginalWidth() * (pageNumber - 1) * zoomToFrameScaleFactor READER_PAGES.style.width = totalPagesWidth() * zoomToFrameScaleFactor + "px" READER_PAGES.style.height = pageOriginalHeight() * zoomToFrameScaleFactor + "px" READER_PAGES.style.left = (- posx * zoomToFrameScaleFactor + zoomToFrameCenteringOffset[0] - previousPagesOffset) + "px" READER_PAGES.style.top = (- posy * zoomToFrameScaleFactor + zoomToFrameCenteringOffset[1]) + "px" CURRENT_PAGE = pageNumber CURRENT_WIDTH = width CURRENT_HEIGHT = height CURRENT_X = posx CURRENT_Y = posy } function refreshReaderDisplay() { moveReaderDisplayToArea(CURRENT_PAGE, CURRENT_WIDTH, CURRENT_HEIGHT, CURRENT_X, CURRENT_Y) } function moveReaderDisplayToPage(pageNumber) { moveReaderDisplayToArea(pageNumber, 0, 0, 0, 0) } function moveReaderDisplayToZoom(index) { moveReaderDisplayToArea(zooms[index][0], zooms[index][1], zooms[index][2], zooms[index][3], zooms[index][4]) CURRENT_ZOOM = index } function updateFocusByWidth(width){ FOCUS_OVERLAY_WIDTH.style.width = (width / READER_CONTENT_FRAME.clientWidth * 100) + "%" FOCUS_OVERLAY_HEIGHT.style.height = "100%" } function updateFocusByHeight(height){ FOCUS_OVERLAY_WIDTH.style.width = "100%" FOCUS_OVERLAY_HEIGHT.style.height = (height / READER_CONTENT_FRAME.clientHeight * 100) + "%" } function toggleViewMode() { if (IS_PAGE_MODE){ if (CURRENT_ZOOM != null){ moveReaderDisplayToZoom(CURRENT_ZOOM) } else { moveReaderDisplayToZoom(getFirstZoomOfPage(CURRENT_PAGE)) } IS_PAGE_MODE = false } else { moveReaderDisplayToPage(CURRENT_PAGE) IS_PAGE_MODE = true } updateProgressBar() } function moveReader(to_next) { if (IS_PAGE_MODE){ if (to_next && CURRENT_PAGE < getPagesCount()) { moveReaderDisplayToPage(CURRENT_PAGE + 1) CURRENT_ZOOM = null } else if (!to_next && CURRENT_PAGE > 1) { moveReaderDisplayToPage(CURRENT_PAGE - 1) CURRENT_ZOOM = null } } else { if (to_next && CURRENT_ZOOM < zooms.length - 1) { moveReaderDisplayToZoom(CURRENT_ZOOM + 1) } else if (!to_next && CURRENT_ZOOM > 0) { moveReaderDisplayToZoom(CURRENT_ZOOM - 1) } } updateProgressBar() } // ============= // CALLBACKS // ============= function handleKeyPress(key){ if (key == MOVE_NEXT) { moveReader(true) } else if (key == MOVE_BACK) { moveReader(false) } else if (key.toUpperCase() == TOGGLE_FULLSCREEN){ if (document.fullscreenElement == null){ READER_FRAME.requestFullscreen(); } else { document.exitFullscreen(); } } else if (key.toUpperCase() == TOGGLE_PROGRESSBAR){ if (PROGRESS_BAR_CONTAINER.hidden == true) { PROGRESS_BAR_CONTAINER.hidden = false } else { PROGRESS_BAR_CONTAINER.hidden = true } refreshReaderDisplay(); } else if (key.toUpperCase() == TOGGLE_VIEW_MODE) { toggleViewMode() } } function handleMouseWhell(deltaY){ if (MOUSEWHELL_WAIT){ return } else { MOUSEWHELL_WAIT = true setTimeout(() => { MOUSEWHELL_WAIT = false }, MOUSEWHELL_MIN_DELAY) } if (deltaY > 0) { moveReader(true, false) } else { moveReader(false, false) } } // ====== // INIT // ====== window.addEventListener("load", (event) => { initReader() }); addEventListener("resize", (event) => { refreshReaderDisplay(); }); addEventListener("keydown", (event) => { handleKeyPress(event.key, event.shiftKey) }); addEventListener("wheel", (event) => { handleMouseWhell(event.deltaY) });