comimi

repository·main·Indexed 19 days ago

https://github.com/yui540/comimi

An open-source, framework-agnostic comic reader library for JS/TS designed to embed a manga viewer into websites. It features support for multiple layout modes (inline, wide, fullscreen), IndexedDB persistence for reading progress and settings, custom i18n translations, and a flexible API for page source resolution and UI customization.

Tokens
8.3K
Snippets
29
Records
40
Agent score
63%

What's inside @yui540/comimi

  1. Understand Layout Modes

    main

    The layoutMode setting determines how the viewer is presented in the browser:

    • inline: Fits within the parent element (max 900px desktop / 500px mobile). Features fixed aspect ratio, rounded corners, and subtle shadows.
    • wide: 100% width, with a draggable handle at the bottom to adjust height.
    • browserFullscreen: Uses position: fixed; inset: 0 to fill the browser window without using the Fullscreen API.
    • nativeFullscreen: Uses the browser's Fullscreen API. Falls back to browserFullscreen if it fails.
  2. Gestures and Interaction

    main

    The viewer supports various touch and mouse gestures:

    • Overlay Toggle: Click/tap in the center 40% of the screen.
    • Page Navigation: Click/tap in the left 30% or right 30% (when overlay is closed). This is an instant switch.
    • Swipe/Drag: Moves pages with a slide animation (40px threshold, horizontal priority).
    • Zooming:
      • Pinch: Standard pinch-to-zoom.
      • Mouse Wheel: Ctrl/⌘ + Wheel zooms centered on the cursor. In spread mode, it only zooms the page under the cursor.
    • Panning: Dragging while zoomed moves the view within page boundaries.
  3. Quickstart with createMangaViewer

    main

    To initialize a manga viewer, import createMangaViewer and pass a container element and an options object. The library will replace the container's existing children with the viewer's DOM.

    import { createMangaViewer } from "@yui540/comimi";
    
    const viewer = createMangaViewer(document.querySelector("#viewer")!, {
      manga: {
        id: "sample",
        title: "モノクロ世界にようこそ",
        author: "yui540",
        pages: [
          { id: "p0", type: "image", src: "/pages/0.webp" },
          { id: "p1", type: "image", src: "/pages/1.webp" },
          { id: "p2", type: "image", src: "/pages/2.webp" }
        ]
      },
      locale: "ja",
      settings: {
        layoutMode: "inline",
        readingDirection: "rtl",
        hasCover: true
      },
      events: {
        pageChange: ({ pageIndex }) => console.log("page", pageIndex + 1)
      }
    });
    
    viewer.nextPage();
    viewer.goToPage(10);
    viewer.destroy();
  4. Quickstart: Create a manga viewer

    main

    To initialize a manga viewer, use the createMangaViewer function. You must provide a DOM element to mount the viewer and a configuration object containing the manga data. The library automatically manages the DOM within the target element. The returned instance can be used to control page navigation, settings, and events.

    import { createMangaViewer } from "@yui540/comimi";
    
    createMangaViewer(document.querySelector("#viewer")!, {
      manga: {
        id: "sample",
        title: "サンプル漫画",
        author: "yui540",
        pages: [
          { id: "p0", type: "image", src: "/pages/0.webp" },
          { id: "p1", type: "image", src: "/pages/1.webp" },
          { id: "p2", type: "image", src: "/pages/2.webp" },
        ],
      },
    });
  5. Install @yui540/comimi via npm

    main

    Install the comimi library using npm to integrate a manga viewer into your website. This library is written in TypeScript/JavaScript and does not depend on UI frameworks like React, making it suitable for standalone use.

    npm install @yui540/comimi
  6. Understand Per-Manga vs Global Settings

    main

    The viewer distinguishes between settings that apply to the entire application (Global) and settings that are specific to a particular manga (Per-Manga).

    Per-Manga Settings

    These are stored in the mangaSettings store and are unique to each manga.id. They include:

    • pageTurnMode
    • hasCover
    • readingDirection

    Global Settings

    All other settings (like locale or autoPageTurnIntervalMs) are stored globally and apply to all manga viewed by the user.

    When calling updateSettings(), the core automatically splits the provided settings into these two categories and persists them accordingly.

  7. Configure IndexedDB persistence and settings priority

    main

    By default, comimi uses IndexedDB to automatically persist global settings (locale, theme, etc.), manga-specific settings (pageTurnMode, hasCover, readingDirection), current layout mode, and the current page per manga.id.

    To prevent persistence, set storage.enabled to false. To change the database name, use storage.databaseName (defaults to manga-viewer).

    Handling initialPageIndex

    • If initialPageIndex is NOT provided: The viewer resumes from the last saved position in IndexedDB. If no position is saved, it starts at page 0.
    • If initialPageIndex IS provided (including 0): The viewer opens at that specific page and does NOT restore the saved position. However, moving pages will continue to update the saved position for next time.

    Overriding saved settings with forceSettings

    Normally, settings act as a seed (used only if no saved value exists). To force specific settings to always use the provided values regardless of what is in IndexedDB, list their keys in the forceSettings array.

    createMangaViewer(container, {
      manga,
      settings: {
        readingDirection: "ltr", // This value is forced
        backgroundColor: "black" // This is a seed; if a saved value exists, it wins
      },
      forceSettings: ["readingDirection"] // Only this key ignores the saved value
    });
  8. Hide UI elements via hiddenSettings

    main

    You can disable specific UI controls using the hiddenSettings array. The behavior depends on whether the key refers to a setting panel item or a toolbar operation.

    Setting Panel Items: (Becomes read-only static display)

    • locale, theme, cover, direction, interval

    Toolbar Operations: (The UI element is completely removed)

    • pageMode (1-page / spread toggle)
    • autoplay (Auto-play button)
    • viewMode (Layout switcher)

    Example:

    createMangaViewer(container, {
      manga,
      hiddenSettings: ["locale", "backgroundColor", "autoplay"]
    });
  9. Configure Viewer Settings

    main

    Settings control the visual and functional behavior of the viewer. Common settings include:

    FieldDefaultDescription
    locale"ja"UI language
    hasCovertrueCover mode (renders 1st page alone during spreads)
    readingDirection"rtl""rtl" (Right-to-Left) or "ltr"
    pageTurnMode"single""single" or "spread" (2-page spread)
    layoutMode"inline""inline", "wide", "browserFullscreen", "nativeFullscreen"
    autoPageTurnIntervalMs5000Auto-play interval in ms
    backgroundColor"white""white" or "black"
    theme"light""light" or "dark"
    zoom.min / .max / .step1 / 4 / 0.25Zoom range and step
  10. Implement internationalization (i18n) with custom translations

    main

    The viewer supports built-in locales: ja, en, zh-CN, ko, th, and id. You can provide a locale string and use the translations option to override or add specific translation keys.

    If a key is missing in the provided translations, it falls back to the ja locale, and finally to the key string itself.

    createMangaViewer(container, {
      manga,
      locale: "fr",
      translations: {
        "overlay.settings": "Réglages",
        "settings.cover": "Couverture"
      }
    });
  11. Resolve page sources for DRM or authenticated access

    main

    Use the resolvePageSrc option to intercept and determine the src for each page image. This is useful for decrypting binaries or adding authentication headers to requests. The function is called just before the image is loaded and the result is cached by manga.id + page.id.

    Important Notes:

    • The library does not automatically revoke Blob URLs. You must manage URL.revokeObjectURL() yourself.
    • If you need to change resolution based on layout (e.g., isSpread), you must manage the cache keys manually as the library caches by page ID.
    • If the resolver rejects, an error icon is displayed.
    // Example: Decrypting an encrypted binary to a Blob URL
    const blobUrls = new Map<string, string>();
    
    createMangaViewer(container, {
      manga,
      resolvePageSrc: async ({ page }) => {
        if (blobUrls.has(page.id)) return blobUrls.get(page.id)!;
    
        const res = await fetch(page.src, { credentials: "include" });
        const encrypted = await res.arrayBuffer();
        const decrypted = await decrypt(encrypted); // User-defined decryption
        const blob = new Blob([decrypted], { type: "image/webp" });
        const url = URL.createObjectURL(blob);
        blobUrls.set(page.id, url);
        return url;
      }
    });
    
    // Cleanup on unload
    window.addEventListener("beforeunload", () => {
      for (const url of blobUrls.values()) URL.revokeObjectURL(url);
    });