428 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			428 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* Melpomene webcomic reader JS */
 | |
| /* Version 1.0.0 - UNSTABLE */
 | |
| /* 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")
 | |
| 
 | |
| //===========================
 | |
| //  STATES GLOBAL VARIABLES
 | |
| //===========================
 | |
| 
 | |
| // The variable ZOOMS can either be defined by another JS file or contructed at init
 | |
| if (typeof PAGES_ZOOMS == 'undefined') {
 | |
|     PAGES_ZOOMS = null
 | |
| }
 | |
| 
 | |
| 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 loadZoomsFromImgTagsIfRequired(){
 | |
|     
 | |
|     // Zooms may be defined by another JS file
 | |
|     if (PAGES_ZOOMS == null){
 | |
|         
 | |
|         PAGES_ZOOMS = []
 | |
|         
 | |
|         // parse the data-zooms of each img and and the page number info
 | |
|         for (var i = 0; i < READER_PAGES.children.length; i++) {
 | |
|             
 | |
|             zooms_raw_data = READER_PAGES.children[i].dataset.zooms
 | |
|             
 | |
|             // ';' separates zooms data, ',' separates values
 | |
|             // We add the page number (adding 1 because of indexing)
 | |
|             zooms = zooms_raw_data.split(";").map(
 | |
|                 zoom => [i + 1].concat(
 | |
|                     zoom.split(',').map(
 | |
|                         value => parseFloat(value)
 | |
|                     )
 | |
|                 )
 | |
|             )
 | |
|             
 | |
|             PAGES_ZOOMS = PAGES_ZOOMS.concat(zooms)
 | |
|         }
 | |
|         
 | |
|     }
 | |
|     
 | |
| }
 | |
| 
 | |
| function getFirstZoomOfPage(pageNumber){
 | |
|    
 | |
|     for (var zoom_idx = 0; zoom_idx < PAGES_ZOOMS.length; zoom_idx++){
 | |
|         if (PAGES_ZOOMS[zoom_idx][0] == pageNumber) {
 | |
|             return zoom_idx
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| function getLastZoomOfPage(pageNumber){
 | |
|     let res = null
 | |
|     
 | |
|     for (var zoom_idx = 0; zoom_idx < PAGES_ZOOMS.length; zoom_idx++){
 | |
|         if (PAGES_ZOOMS[zoom_idx][0] == pageNumber) {
 | |
|             res = zoom_idx
 | |
|         }
 | |
|         
 | |
|         if (res != null && PAGES_ZOOMS[zoom_idx][0] != pageNumber) {
 | |
|             break
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     return res
 | |
| }
 | |
| 
 | |
| function getZoomCountForPage(pageNumber) {
 | |
|     return PAGES_ZOOMS.filter(zoom => zoom[0] == pageNumber).length
 | |
| }
 | |
| 
 | |
| function getCurrentZoomIndexForPage() {
 | |
|     previousZoomsCount = PAGES_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(){
 | |
|     loadZoomsFromImgTagsIfRequired()
 | |
|     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){
 | |
|     
 | |
|     // First, scale so the page is at scale 1 compared to the frame
 | |
|     READER_PAGES.style.transform = "scale(" + totalPagesWidth() / READER_CONTENT_FRAME.clientWidth + ")"
 | |
|     
 | |
|     // Then, move to the correct page
 | |
|     READER_PAGES.style.transform = "translateX(" + (- pageOriginalWidth() * (pageNumber - 1)) + "px )" + READER_PAGES.style.transform
 | |
|     
 | |
|     // Then move so the top-left point of the zoom match the frame top-left
 | |
|     READER_PAGES.style.transform = "translate(" + (- posx) + "px, " + (-posy) + "px )" + READER_PAGES.style.transform
 | |
|     
 | |
|     // Then, scale so the zoom would fit the frame, and center the zoom
 | |
|     if (width == 0){
 | |
|         width = pageOriginalWidth()
 | |
|     }
 | |
|     
 | |
|     if (height == 0){
 | |
|         height = pageOriginalHeight()
 | |
|     }
 | |
|     
 | |
|     zoomRatio = width / height
 | |
|     
 | |
|     if (readerFrameRatio() > zoomRatio) {
 | |
|         // Frame wider than zoom => scale so heights are the same, offset on x
 | |
|         var zoomToFrameScaleFactor = READER_CONTENT_FRAME.clientHeight / height
 | |
|         
 | |
|         READER_PAGES.style.transform = "scale(" + zoomToFrameScaleFactor + ")" + READER_PAGES.style.transform
 | |
|         
 | |
|         var scaledWidth = width * zoomToFrameScaleFactor
 | |
|         var offset = (READER_CONTENT_FRAME.clientWidth - scaledWidth) / 2
 | |
|         
 | |
|         READER_PAGES.style.transform = "translateX(" + offset + "px)" + READER_PAGES.style.transform
 | |
|         
 | |
|         updateFocusByWidth(scaledWidth)
 | |
|     } else {
 | |
|         // Frame narower than zoom => scale so left/right match, offset on y
 | |
|         var zoomToFrameScaleFactor = READER_CONTENT_FRAME.clientWidth / width 
 | |
|         
 | |
|         READER_PAGES.style.transform = "scale(" + zoomToFrameScaleFactor + ")" + READER_PAGES.style.transform
 | |
|         
 | |
|         var scaledHeight = height * zoomToFrameScaleFactor
 | |
|         var offset = (READER_CONTENT_FRAME.clientHeight - scaledHeight) / 2
 | |
|         
 | |
|         READER_PAGES.style.transform = "translateY(" + offset + "px)" + READER_PAGES.style.transform
 | |
|         
 | |
|         updateFocusByHeight(scaledHeight)
 | |
|     }
 | |
|     
 | |
|     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(PAGES_ZOOMS[index][0], PAGES_ZOOMS[index][1], PAGES_ZOOMS[index][2], PAGES_ZOOMS[index][3], PAGES_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 < PAGES_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)
 | |
| });
 |