未命名 文件名edit icon

创建者:
用户eo7M6pZ1
Fork(复制)
下载
嵌入
BUG反馈
index.html
备用.css
index.html
            
            <!DOCTYPE html>
<html lang="zh">

<head>
    <title>瀑布流图片浏览器</title>
    <meta charset="UTF-8" />
    <meta name="color-scheme" content="dark" />
    <meta name="theme-color" content="black" />
    <meta name="description" content="Masonry Imgage Viewer" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="icon" href="favicon.ico" />
    <link rel="stylesheet" href="style.css" />
    <link rel="manifest" href="app.webmanifest" />

    <style>
        :root {
          --colgap: 0px;
          --rowgap: 0px;
          --imgradius: 0px;
          --imgborder: 0px;
          --opacity: 1;
          --halfwhite: rgba(255, 255, 255, 0.5);
          --halfblack: rgba(0, 0, 0, 0.5);
          --bg-color: #000;
          --bg-image: none;
        }
        body {
          margin: 0;
          background-color: var(--bg-color, #000);
          background-image: var(--bg-image, none);
          background-size: cover;
          background-position: center;
          background-repeat: no-repeat;
          background-attachment: fixed;
          color: white;
          display: flex;
          overflow-y: scroll;
        }
        #hint {
          position: fixed;
          height: 100%;
          width: 100%;
          display: grid;
          place-items: center;
          font-size: xx-large;
        }
        #cursorplace {
          position: fixed;
          height: 100%;
          width: 1px;
          z-index: 1;
        }
        #indicator {
          position: fixed;
          top: 0;
          right: 17px;
          width: 50px;
          height: 50px;
          place-items: center;
          pointer-events: none;
          background-color: var(--halfblack);
          border-radius: 50%;
          font-size: large;
          animation: scrollmove linear;
          animation-timeline: scroll();
          display: none;
        }
        #indicator.show {
          display: grid;
        }
        @keyframes scrollmove {
          from {
            top: 0;
          }
          to {
            top: calc(100vh - 50px);
          }
        }
        #imgbox {
          flex: 1;
          display: flex;
          flex-wrap: wrap;
          line-height: 0;
          column-gap: var(--colgap);
        }
        .colflex {
          align-items: flex-start;
        }
        .rowflex::after {
          content: "";
          flex-grow: 114514;
        }
        .rowflex {
          row-gap: var(--rowgap);
        }
        .imgcol {
          flex: 1;
          display: flex;
          flex-direction: column;
          row-gap: var(--rowgap);
        }
        .wrap {
          position: relative;
          cursor: pointer;
        }
      .wrap {
  position: relative;
  cursor: pointer;
}
.wrap {
  position: relative;
  cursor: pointer;
}

.wrap > img {
  width: 100%;
  height: auto;
  box-sizing: border-box;
  border: solid white;
  border-radius: var(--imgradius);
  border-width: var(--imgborder);
}

/* 移除悬停显示的样式 */
/* .wrap:hover > .info {
  visibility: visible;
} */

.info {
  position: absolute;
  bottom: 0;
  width: 100%;
  text-align: center;
  line-height: normal;
  border-radius: 10px;
  background-color: var(--halfblack);
  visibility: visible; /* 修改为可见 */
}

.info > span {
  cursor: text;
  word-break: break-all;
}
        #cover {
          position: fixed;
          z-index: 2;
          top: 0;
          left: 0;
          width: 100%;
          height: 100%;
          outline: none;
          background-color: var(--halfblack);
          display: none;
        }
        #cover.show {
          display: block;
        }
        .zoom {
          position: fixed;
        }
        #previmg,
        #nextimg {
          top: 50%;
          opacity: 0.5;
        }
        #previmg {
          left: 1%;
        }
        #nextimg {
          right: 1%;
        }
        #previmg:hover,
        #nextimg:hover {
          opacity: 1;
        }
        .floatbtn {
          position: fixed;
          z-index: 1;
          width: 50px;
          height: 50px;
          display: grid;
          place-items: center;
          outline: none;
          border: medium solid white;
          border-radius: 50%;
          background-color: var(--halfblack);
          color: white;
          font-size: xx-large;
          cursor: pointer;
        }
        #sidebtn {
          top: 20px;
          left: 20px;
          opacity: var(--opacity);
        }
        #sidebtn:hover {
          opacity: 1;
        }
        #treebtn {
          top: calc(50% - 35px);
          left: -30px;
          height: 70px;
          display: flex;
          justify-content: end;
          opacity: var(--opacity);
        }
        #treebtn:hover {
          opacity: 1;
        }
        .scrollbtn {
          position: fixed;
          z-index: 1;
          width: 40px;
          height: 40px;
          outline: none;
          border: medium solid white;
          background-color: var(--halfblack);
          opacity: var(--opacity);
          user-select: none;
          cursor: pointer;
        }
        .scrollbtn:hover {
          opacity: 1;
        }
        #totop {
          bottom: 78px;
          right: 40px;
          border-radius: 10px 10px 0 0;
        }
        #toend {
          bottom: 40px;
          right: 40px;
          border-radius: 0 0 10px 10px;
        }
        #sidebar {
          position: fixed;
          left: -400px;
          display: grid;
          align-items: center;
          grid-auto-rows: min-content;
          grid-template-columns: repeat(6, 50px);
          column-gap: 10px;
          row-gap: 10px;
          border-radius: 20px;
          padding: 20px;
          background-color: var(--halfblack);
          white-space: nowrap;
          z-index: 1;
        }
        #sidebar.show {
          left: 0;
        }
        #sidebar > button {
          justify-self: center;
          outline: none;
          padding: 5px 10px 5px 10px;
          border: medium solid var(--halfwhite);
          border-radius: 50px;
          background-color: var(--halfblack);
          cursor: pointer;
        }
        #sidebar > button:hover {
          border-color: white;
        }
        #sidebar > button:active {
          background: var(--halfwhite);
        }
        #sidebar > button.active {
          background: var(--halfwhite);
        }
        #treebar {
          position: fixed;
          z-index: 2;
          background-color: var(--halfblack);
          display: none;
        }
        #treebar.show {
          display: block;
        }
        #resizebar {
          min-width: 200px;
          max-width: 50vw;
          resize: horizontal;
          overflow: scroll;
          opacity: 0.5;
        }
        #resizebar::-webkit-scrollbar {
          width: 20px;
          height: 100vh;
        }
        #resizebar::-webkit-resizer {
          background-color: white;
        }
        #dirtree {
          position: absolute;
          width: calc(100% - 25px);
          height: 100vh;
          direction: rtl;
          overflow-y: scroll;
          line-height: 1;
          padding-right: calc(50vw - 100%);
        }
        #dirtree > ul {
          margin: 0;
        }
        ul {
          direction: ltr;
          margin: 0 0 0 10px;
          padding: 0;
          border-left: medium solid var(--halfwhite);
        }
        li {
          position: relative;
          z-index: 1;
          min-width: 100%;
          overflow: hidden;
          white-space: nowrap;
          text-overflow: ellipsis;
          list-style: "📁" inside;
          padding-bottom: 2px;
          border-radius: 25px;
          border: medium solid transparent;
          cursor: pointer;
        }
        li:hover {
          width: fit-content;
          border-color: white;
          background-color: var(--halfblack);
        }
        li.active {
          background-color: var(--halfwhite);
        }
        li.visited {
          opacity: 0.75;
        }
        select {
          justify-self: center;
          padding: 5px;
          outline: none;
          text-align: center;
          font-size: medium;
          cursor: pointer;
          border-radius: 50px;
          background-color: var(--halfblack);
          border: medium solid var(--halfwhite);
        }
        select:hover {
          border-color: white;
        }
        [type="text"] {
          justify-self: center;
          box-sizing: border-box;
          width: 100%;
          padding: 5px;
          outline: none;
          text-align: center;
          font-size: medium;
          border-radius: 50px;
          border: medium solid var(--halfwhite);
          background-color: var(--halfblack);
        }
        [type="text"]:hover {
          border-color: white;
        }
        [type="range"] {
          appearance: none;
          width: 2 00px;
          height: 10px;
          border-radius: 10px;
          border: medium solid white;
          background-color: var(--halfblack);
          opacity: 0.5;
        }
        [type="range"]:hover {
          opacity: 1;
        }
        ::-webkit-slider-thumb {
          appearance: none;
          width: 30px;
          height: 30px;
          cursor: pointer;
          border-radius: 50%;
          border: medium solid white;
          background-color: var(--halfblack);
          opacity: 0.5;
        }
        ::-webkit-slider-thumb:hover {
          opacity: 1;
        }
        .span2 {
          grid-column: span 2;
        }
        .span3 {
          grid-column: span 3;
        }
        .span4 {
          grid-column: span 4;
        }
        .tootip {
          display: none;
        }
        .tip:hover .tootip {
          display: block;
          position: absolute;
          background-color: black;
          padding: 10px;
          border-radius: 10px;
        }
        ::selection {
          background-color: white;
          color: black;
        }
        .background-settings {
          grid-column: span 6;
          display: flex;
          flex-wrap: wrap;
          gap: 5px;
          margin: 5px 0;
          align-items: center;
          justify-content: start;
        }
        .background-settings input[type="color"] {
          width: 100px;
          height: 30px;
          padding: 0;
          border: none;
          border-radius: 4px;
          cursor: pointer;
        }
        .background-settings button {
          height: 30px;
          padding: 0 10px;
          border: 1px solid #666;
          border-radius: 4px;
          background: #333;
          color: #fff;
          cursor: pointer;
        }
        .background-settings button:hover {
          background: #444;
        }
        .tip {
          grid-column: span 6;
          margin: 5px 0;
        }
        #sidebar > div:not(.background-settings):not(.tip) {
          display: flex;
          align-items: center;
        }
        #sidebar input,
        #sidebar button,
        #sidebar select {
          margin: auto 0;
        }
        
    </style>
</head>

<body>
    <div id="hint">点击或拖入图片文件夹</div>
    <div id="cursorplace"></div>
    <div id="imgbox"></div>
    <div id="indicator"></div>
    <div id="cover" tabindex="1">
        <button id="previmg" class="floatbtn">&lt;</button>
        <button id="nextimg" class="floatbtn">&gt;</button>
    </div>
    <button class="floatbtn" id="treebtn">&gt;</button>
    <button class="floatbtn" id="sidebtn">+</button>
    <button class="scrollbtn" id="toend"></button>
    <button class="scrollbtn" id="totop"></button>
    <div id="treebar">
        <div id="dirtree">
            <ul></ul>
        </div>
        <div id="resizebar"></div>
    </div>
    <div id="sidebar">
        <div>列距:</div>
        <input type="range" id="colgapinput" min="0" max="40" value="20" class="span4" />
        <div id="colgap">0</div>
        <div>行距:</div>
        <input type="range" id="rowgapinput" min="0" max="40" value="20" class="span4" />
        <div id="rowgap">0</div>
        <div>圆角:</div>
        <input type="range" id="imgradiusinput" min="0" max="40" value="20" class="span4" />
        <div id="imgradius">0</div>
        <div>边框:</div>
        <input type="range" id="imgborderinput" min="0" max="5" value="0" class="span4" />
        <div id="imgborder">0</div>
        <div>列数:</div>
        <button id="colflex">纵向</button>
        <input type="range" id="colcountinput" min="1" max="8" value="4" class="span3" />
        <div id="colcount">4</div>
        <div>高度:</div>
        <button id="rowflex">横向</button>
        <input type="range" id="minheightinput" min="100" max="500" value="300" step="10" class="span3" />
        <div id="minheight">250</div>
        <div>总数:</div>
        <div id="totalcount"></div>
        <div>加载:</div>
        <div id="loadedcount"></div>
        <div>显示:</div>
        <div id="showcount"></div>
        <button id="loadall">加载</button>
        <button id="pause">暂停</button>
        <div>跳转:</div>
        <input id="jumpTo" type="text" />
        <div>每次:</div>
        <input id="perload" type="text" value="25" />
        <div>排序:</div>
        <select id="sortby" class="span2">
            <option value="default">默认</option>
            <option value="lastModified">日期</option>
            <option value="size">大小</option>
            <option value="random">随机</option>
            <option value="folderRandom">多文件夹随机</option>
        </select>
        <select id="order" class="span2">
            <option value="asc">升序</option>
            <option value="desc">降序</option>
        </select>
        <button id="resort">重排</button>
        <div>过滤:</div>
        <button id="filtmono" class="span2">黑白漫画</button>
        <button id="filtborder" class="span2">纯色边框</button>
        <button id="revert">反转</button>
        <div class="tip">
            背景:
        </div>
        <div class="background-settings">
            <input type="color" id="bgColor" class="span2" value="#000000" />
            <!-- <button id="bgImageBtn" class="span2">选择图片</button>
        <input type="file" id="bgImageInput" accept="image/*" style="display: none;" /> -->
            <button id="clearBg" class="span2">清除背景</button>
        </div>
        <div class="tip">
            宽高:
            <div class="tootip">
                例:<br />
                "16:9"表示宽高比等于16:9<br />
                "16:9-"表示宽高比大于16:9<br />
                "-16:9"表示宽高比小于16:9<br />
                "4:3-16:9"表示宽高比在4:3到16:9之间<br />
            </div>
        </div>
        <input type="text" id="aspectratio" class="span3" />
        <div class="tip">自动滚动:</div>
        <button id="autoscroll" class="span2">开始</button>
        <input type="range" id="scrollspeed" min="1" max="10" value="3" class="span2" />
        <div id="speedvalue">3</div>
        <select id="scrollMode" class="span2">
            <option value="down">向下滚动</option>
            <option value="updown">上下往复</option>
        </select>
    </div>
    <script src="script.js"></script>

    <script>
        //倒排
        //缓存
        //历史
        //复制 移动 删除
        navigator.serviceWorker.register("sw.js");
        class Queue {
          constructor(items = []) {
            this.items = items;
            this.getters = [];
          }
          push(item) {
            this.items.push(item);
            if (this.getters.length > 0) this.getters.shift()(this.items.shift());
          }
          shift() {
            if (this.items.length === 0)
              return new Promise((resolve) => this.getters.push(resolve));
            return this.items.shift();
          }
        }
        class Semaphore {
          constructor(value = 1) {
            this.value = value;
            this.queue = [];
          }
          async acquire() {
            if (this.value > 0) this.value--;
            else return new Promise((resolve) => this.queue.push(resolve));
          }
          release() {
            this.value++;
            if (this.queue.length > 0) this.queue.shift()();
          }
        }
        DataView.prototype.getUint24 = function (byteOffset, littleEndian) {
          if (littleEndian) {
            return (
              this.getUint8(byteOffset) |
              (this.getUint8(byteOffset + 1) << 8) |
              (this.getUint8(byteOffset + 2) << 16)
            );
          } else {
            return (
              (this.getUint8(byteOffset) << 16) |
              (this.getUint8(byteOffset + 1) << 8) |
              this.getUint8(byteOffset + 2)
            );
          }
        };
        function throttle(func, ms = 1000) {
          let timeout;
          let con = this;
          return function () {
            if (timeout) return;
            func.apply(con, arguments);
            timeout = setTimeout(() => (timeout = null), ms);
          };
        }
        function debounce(func, ms = 1000) {
          let timeout;
          let con = this;
          return function () {
            clearTimeout(timeout);
            timeout = setTimeout(() => func.apply(con, arguments), ms);
          };
        }
        function range(start, end, step = 1) {
          let arr = [];
          for (let i = start; i < end; i += step) arr.push(i);
          return arr;
        }
        function flatObj(obj, path) {
          obj = path.split("/").reduce((obj, name) => obj[name], obj);
          function recurse(obj) {
            for (let key in obj) {
              if (typeof obj[key] === "object") recurse(obj[key]);
              else arr.push(obj[key]);
            }
          }
          let arr = [];
          recurse(obj);
          return arr;
        }
        let MB = 1024 ** 2;
        let GB = 1024 ** 3;
        function formatSize(bytes) {
          if (bytes < 1024) return `${bytes} B`;
          else if (bytes < MB) return `${(bytes / 1024).toFixed(2)} KB`;
          else if (bytes < GB) return `${(bytes / MB).toFixed(2)} MB`;
        }
        let sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
        let log = console.log;
        let docEl = document.documentElement;
        let getEl = (id) => document.getElementById(id);
        let newEl = (tag) => document.createElement(tag);
        // #region elIds
        let aspectratio = getEl("aspectratio");
        let colcountinput = getEl("colcountinput");
        let cover = getEl("cover");
        let cursorplace = getEl("cursorplace");
        let dirtree = getEl("dirtree");
        let filtborder = getEl("filtborder");
        let filtmono = getEl("filtmono");
        let hint = getEl("hint");
        let imgbox = getEl("imgbox");
        let indicator = getEl("indicator");
        let jumpTo = getEl("jumpTo");
        let loadall = getEl("loadall");
        let loadedcount = getEl("loadedcount");
        let minheightinput = getEl("minheightinput");
        let nextimg = getEl("nextimg");
        let order = getEl("order");
        let pause = getEl("pause");
        let perload = getEl("perload");
        let previmg = getEl("previmg");
        let resort = getEl("resort");
        let revert = getEl("revert");
        let showcount = getEl("showcount");
        let sidebar = getEl("sidebar");
        let sidebtn = getEl("sidebtn");
        let sortby = getEl("sortby");
        let toend = getEl("toend");
        let totalcount = getEl("totalcount");
        let totop = getEl("totop");
        let treebar = getEl("treebar");
        let treebtn = getEl("treebtn");
        let bgColor = getEl("bgColor");
        let bgImageBtn = getEl("bgImageBtn");
        let bgImageInput = getEl("bgImageInput");
        let clearBg = getEl("clearBg");
        let autoscroll = getEl("autoscroll");
        let scrollspeed = getEl("scrollspeed");
        let speedvalue = getEl("speedvalue");
        let scrollMode = getEl("scrollMode");
        // #endregion
        let zoom;
        let flextype;
        let currdir;
        let minCol;
        let minR, maxR;
        let held = false;
        let scrollAnimation = null;
        totalcount.value = 0;
        loadedcount.value = 0;
        showcount.value = 0;
        let dircount = 0;
        let loadingAll = 0;
        let loading = 0;
        let filting = 0;
        let imgcols = [];
        let marks = [];
        let allData = new Map();
        let toLoad = new Queue();
        let visImgs = new Set();
        let configs = [
          "colgap",
          "rowgap",
          "imgradius",
          "imgborder",
          "colcount",
          "minheight",
        ];
        let enums = {
          colflex: "colflex",
          rowflex: "rowflex",
          default: "default",
          name: "name",
          date: "date",
          size: "size",
          random: "random",
          folderRandom: "folderRandom",
          asc: "asc",
          desc: "desc",
        };
        let imgObs = new IntersectionObserver((es) => {
          es.forEach((e) => {
            if (e.isIntersecting) visImgs.add(e.target.index);
            else visImgs.delete(e.target.index);
          });
        });
        let dirObs = new IntersectionObserver((es) => {
          es.forEach((e) => {
            if (e.isIntersecting) {
              currdir?.classList.remove("active");
              currdir?.classList.add("visited");
              currdir = getEl("li" + e.target.index);
              currdir.classList.add("active");
            }
          });
        });
        function initSort() {
          ["sortby", "order", "perload"].forEach((id) => {
            let store = localStorage.getItem(id);
            let select = getEl(id);
            if (store) select.value = store;
            select.onchange = (e) => localStorage.setItem(id, e.target.value);
          });
        }
        function initFilt() {
          ["filtmono", "filtborder", "revert"].forEach((id) => {
            let store = localStorage.getItem(id);
            let button = getEl(id);
            button.active = store === "true";
            if (button.active) button.classList.add("active");
            button.onclick = (e) => {
              let button = e.target;
              button.classList.toggle("active");
              button.active = button.classList.contains("active");
              localStorage.setItem(button.id, button.active);
              if (!filtmono.active && !filtborder.active && revert.active) return;
              reflow();
            };
          });
        }
        function initFlex() {
          let store = localStorage.getItem("flextype");
          flextype = store !== null ? store : enums.colflex;
          getEl(flextype).classList.add("active");
          imgbox.className = flextype;
          ["colflex", "rowflex"].forEach((id, i, arr) => {
            let el = getEl(id);
            el.onclick = (e) => {
              let button = e.target;
              if (flextype === button.id) return;
              flextype = button.id;
              localStorage.setItem("flextype", flextype);
              imgbox.className = flextype;
              arr.forEach((el) => getEl(el).classList.remove("active"));
              button.classList.add("active");
              reflow();
            };
          });
        }
        function initConfig(id) {
          let input = getEl(id + "input");
          input.oninput = (e) => (getEl(id).innerText = e.target.value);
          input.onchange = (e) => {
            let val = e.target.value;
            configs[id] = val;
            docEl.style.setProperty("--" + id, val + "px");
            localStorage.setItem(id, val);
          };
          let store = localStorage.getItem(id);
          if (store) input.value = store;
          input.onchange({ target: input });
          input.oninput({ target: input });
        }
        function initBackground() {
          let storedBgColor = localStorage.getItem("bgColor");
          // let storedBgImage = localStorage.getItem("bgImage");
          
          if (storedBgColor) {
            bgColor.value = storedBgColor;
            docEl.style.setProperty("--bg-color", storedBgColor);
          }
          
          // if (storedBgImage) {
          //   docEl.style.setProperty("--bg-image", `url(${storedBgImage})`);
          // }
          
          bgColor.addEventListener("input", (e) => {
            let color = e.target.value;
            docEl.style.setProperty("--bg-color", color);
            localStorage.setItem("bgColor", color);
          });
          
          // bgImageBtn.addEventListener("click", () => {
          //   bgImageInput.click();
          // });
          
          // bgImageInput.addEventListener("change", async (e) => {
          //   let file = e.target.files[0];
          //   if (!file) return;
            
          //   if (!file.type.startsWith("image/")) {
          //     alert("请选择图片文件");
          //     return;
          //   }
            
          //   let reader = new FileReader();
          //   reader.onload = (e) => {
          //     let imageUrl = e.target.result;
          //     docEl.style.setProperty("--bg-image", `url(${imageUrl})`);
          //     localStorage.setItem("bgImage", imageUrl);
          //   };
          //   reader.readAsDataURL(file);
          // });
          
          clearBg.addEventListener("click", () => {
            docEl.style.setProperty("--bg-color", "#000000");
            docEl.style.setProperty("--bg-image", "none");
            bgColor.value = "#000000";
            localStorage.removeItem("bgImage");
            localStorage.setItem("bgColor", "#000000");
          });
        }
        document.ondrop = async (e) => {
          if (e.dataTransfer.types[0] !== "Files") return;
          e.preventDefault();
          let items = [...e.dataTransfer.items].map((item) =>
            item.getAsFileSystemHandle()
          );
          initLoad();
          await handle(items);
          if (sortby.value !== enums.default || order.value !== enums.asc) reflow();
        };
        document.onpaste = async (e) => {
          if (e.clipboardData.types[0] !== "Files") return;
          let items = [...e.clipboardData.items].map((item) =>
            item.getAsFileSystemHandle()
          );
          initLoad();
          await handle(items);
          if (sortby.value !== enums.default || order.value !== enums.asc) reflow();
        };
        hint.onclick = async () => {
          let items = await showDirectoryPicker({
            mode: "readwrite",
            startIn: "pictures",
          });
          initLoad();
          await handle(items.values());
          if (sortby.value !== enums.default || order.value !== enums.asc) reflow();
        };
        function initLoad() {
          if (loadedcount.value > 0) return;
          if (sortby.value === enums.default && order.value === enums.asc) reflow();
          docEl.style.setProperty("--opacity", "0");
          hint.remove();
        }
        async function handle(items, dir = "", folderUl = dirtree.children[0]) {
          for await (let item of items) {
            let name = item.name;
            let path = dir + "/" + name;
            if (allData.has(path)) continue;
            if (item.kind === "directory") {
              dircount++;
              let val = totalcount.value,
                index = val + dircount;
              let li = newEl("li");
              li.innerText = name;
              li.id = "li" + index;
              li.index = index;
              folderUl.appendChild(li);
              let ul = newEl("ul");
              folderUl.appendChild(ul);
              toLoad.push(path);
              allData.set(path, index);
              await handle(item.values(), path, ul);
              if (val === totalcount.value) {
                li.style.display = "none";
                ul.style.display = "none";
              }
            }
            if (item.kind === "file") {
              let file = await item.getFile();
              if (!file.type.match(/image.*/)) continue;
              totalcount.value++;
              file.dir = dir;
              file.path = path;
              file.index = totalcount.value + dircount;
              allData.set(path, { file });
              toLoad.push(path);
              totalcount.innerText = totalcount.value;
            }
          }
        }
        function reflow(index = 0) {
          if (filting) return;
          filting = 1;
          let type = sortby.value;
          let ord = order.value;
          let imgs = [...imgbox.children];
          if (type !== enums.default) {
            if (type === enums.folderRandom) {
              // 按文件夹分组
              let folderGroups = new Map();
              toLoad.items.forEach(path => {
                let data = allData.get(path);
                if (typeof data === "object") {
                  let dir = data.file.dir;
                  if (!folderGroups.has(dir)) {
                    folderGroups.set(dir, []);
                  }
                  folderGroups.get(dir).push(path);
                }
              });
              
              // 随机打乱每个文件夹内的文件
              for (let files of folderGroups.values()) {
                for (let i = files.length - 1; i > 0; i--) {
                  let j = Math.floor(Math.random() * (i + 1));
                  [files[i], files[j]] = [files[j], files[i]];
                }
              }
              
              // 将所有文件夹的文件交错组合
              let maxLength = Math.max(...folderGroups.values().map(arr => arr.length));
              let newItems = [];
              for (let i = 0; i < maxLength; i++) {
                for (let files of folderGroups.values()) {
                  if (i < files.length) {
                    newItems.push(files[i]);
                  }
                }
              }
              toLoad.items = newItems;
            } else {
              imgs.sort((a, b) => {
                if (type === enums.random) {
                  return Math.random() - 0.5;
                }
                let va = a.dataset[type];
                let vb = b.dataset[type];
                return ord === enums.asc ? va - vb : vb - va;
              });
            }
          }
          if (!filtmono.active && !filtborder.active && revert.active) return;
          imgbox.querySelectorAll(".mark").forEach((el) => {
            el.remove();
          });
          minCol = imgbox;
          marks = [];
          visImgs.clear();
          imgcols = [];
          if (flextype === enums.colflex) {
            for (let _ of Array(parseInt(colcountinput.value))) {
              let imgcol = newEl("div");
              imgcol.className = "imgcol";
              imgcols.push(imgcol);
              imgcol.onmouseout = addInfo;
            }
          }
          imgbox.replaceChildren(...imgcols);
          loading = 0;
          if (showcount.value > 0) {
            toLoad.getters.forEach((rsv) => rsv());
            toLoad.items = [...allData.keys()];
            showcount.value = 0;
          }
          if (index > 0) toLoad.items = toLoad.items.slice(index);
          if (type !== enums.default && type !== enums.folderRandom)
            toLoad.items = toLoad.items
              .filter((p) => typeof allData.get(p) === "object")
              .sort((a, b) => allData.get(a).file[type] - allData.get(b).file[type]);
          if (ord === enums.desc) toLoad.items.reverse();
          loadNext();
          filting = 0;
        }
        async function loadNext() {
          if (
            loading > 0 ||
            (!loadingAll &&
              docEl.scrollTop + docEl.clientHeight <
                minCol.scrollHeight - docEl.clientHeight)
          )
            return;
          for (let _ of Array(parseInt(perload.value))) {
            let path = await toLoad.shift();
            if (!path) return;
            let data = allData.get(path);
            if (typeof data === "number") {
              let mark = newEl("div");
              mark.index = data;
              dirObs.observe(mark);
              marks.push(mark);
              continue;
            }
            let file = data.file;
            let w, h, img;
            if (maxR) {
              if (file.width) [w, h] = [file.width, file.height];
              else {
                [w, h, img] = await getWH(file);
                [file.width, file.height] = [w, h];
                if (img) data.img = img;
              }
              if (h * minR > w + 2 || h * maxR < w - 2) continue;
            }
            let wrap = data.wrap;
            if (wrap) {
              loadImg(wrap);
              continue;
            }
            let onloaded = () => {
              URL.revokeObjectURL(img.src);
              loadedcount.innerText = loadedcount.value++ + 1;
              let wrap = newEl("div");
              wrap.className = "wrap";
              wrap.appendChild(img);
              data.wrap = wrap;
              loadImg(wrap);
              loading--;
              loadNext();
            };
            if (data.img) {
              img = data.img;
              onloaded();
            } else {
              img = new Image();
              data.img = img;
              loading++;
              img.onload = onloaded;
              img.onerror = onloaded;
              img.src = URL.createObjectURL(file);
            }
            img.index = file.index;
            img.alt = file.name;
            img.path = file.path;
          }
          setTimeout(loadNext, 0);
        }
        function addInfo(e) {
          let img = e.relatedTarget;
          if (img?.tagName !== "IMG") return;
          let wrap = img.parentElement;
          if (wrap.hasInfo) return;
          let file = allData.get(img.path).file;
          let imgInfo = [
            formatSize(file.size),
            `${img.naturalWidth}x${img.naturalHeight}`,
            file.lastModifiedDate.toLocaleString().replaceAll("/", "-"),
            file.name,
            file.dir,
          ];
          imgInfo = imgInfo.flatMap((t) => {
            let span = newEl("span");
            span.innerText = t;
            let br = newEl("br");
            return [span, br];
          });
          let infoBar = newEl("div");
          infoBar.classList.add("info");
          infoBar.replaceChildren(...imgInfo);
          wrap.appendChild(infoBar);
          wrap.hasInfo = 1;
        }
        function loadImg(wrap) {
          let img = wrap.children[0];
          if (
            ((filtmono.active && isMono(img)) ||
              (filtborder.active && isMonoBorder(img))) ^ revert.active
          )
            return;
          imgObs.observe(wrap);
          wrap.removeAttribute("style");
          showcount.innerText = showcount.value++ + 1;
          wrap.id = "img" + showcount.value;
          wrap.index = showcount.value;
          if (img.index > marks[0]?.index) wrap.appendChild(marks.shift());
          if (flextype === enums.colflex) {
            minCol = imgcols.reduce((prev, curr) =>
              prev.offsetHeight <= curr.offsetHeight ? prev : curr
            );
            minCol.appendChild(wrap);
          } else {
            resize(wrap);
            imgbox.appendChild(wrap);
          }
        }
        function resize(wrap) {
          let img = wrap.children[0],
            ratio = img.naturalWidth / img.naturalHeight;
          wrap.style.flexBasis = ratio * minheightinput.value + "px";
          wrap.style.flexGrow = ratio;
        }
        function isMonoBorder(img) {
          if (img.isMonoBorder !== undefined) return img.isMonoBorder;
          let { ctx, width, height } = getThumb(img);
          let d = (x, y, w, h) => ctx.getImageData(x, y, w, h).data;
          let wasd = [
            ...d(0, 0, 1, height),
            ...d(0, 0, width, 1),
            ...d(width - 1, 0, 1, height),
            ...d(0, height - 1, width, 1),
          ];
          let bns = [];
          for (let i of range(0, wasd.length, 4))
            bns.push(Math.round((wasd[i] + wasd[i + 1] + wasd[i + 2]) / 3 / 4));
          let counts = bns.reduce(
            (acc, curr) => acc.set(curr, (acc.get(curr) || 0) + 1),
            new Map()
          );
          if (Math.max(...counts.values()) > 0.0625 * wasd.length) {
            img.isMonoBorder = true;
            return true;
          }
          img.isMonoBorder = false;
          return false;
        }
        function isMono(img) {
          if (img.isMono !== undefined) return img.isMono;
          let { ctx, width, height } = getThumb(img);
          let pixels = width * height,
            data = ctx.getImageData(0, 0, width, height).data;
          for (let area of range(0, 4)) {
            let r = 0,
              g = 0,
              b = 0;
            for (let i of range(area * pixels, (area + 1) * pixels, 4)) {
              r += data[i];
              g += data[i + 1];
              b += data[i + 2];
            }
            if (Math.max(r, g, b) - Math.min(r, g, b) > pixels) {
              img.isMono = false;
              return false;
            }
          }
          img.isMono = true;
          return true;
        }
        function getThumb(img, l = 100) {
          let data = allData.get(img.path);
          if (l === 100) {
            let thumb = data.thumb;
            if (thumb) return thumb;
          }
          let canvas = newEl("canvas"),
            ctx = canvas.getContext("2d", { willReadFrequently: true }),
            wh = [img.naturalWidth, img.naturalHeight],
            m = Math.max(...wh),
            r = l / m;
          wh = wh.map((n) => Math.round(n * r));
          [canvas.width, canvas.height] = wh;
          ctx.drawImage(img, 0, 0, ...wh);
          let thumb = { canvas, ctx, width: wh[0], height: wh[1] };
          if (l === 100) data.thumb = thumb;
          return thumb;
        }
        function toggleZoom(e) {
          let oriimg = e.target;
          if (oriimg.className === "info") {
            if (!getSelection().isCollapsed) return;
            oriimg = oriimg.parentElement.children[0];
          } else if (oriimg.tagName !== "IMG") return;
          let rep = new Image();
          rep.id = "rep";
          rep.width = oriimg.naturalWidth;
          rep.height = oriimg.naturalHeight;
          oriimg.replaceWith(rep);
          zoom = oriimg;
          zoom.className = "zoom";
          zoom.style.top = (docEl.clientHeight - zoom.height) / 2 + "px";
          zoom.style.left = (docEl.clientWidth - zoom.width) / 2 + "px";
          zoom.scale =
            Math.min(
              docEl.clientHeight / zoom.height,
              docEl.clientWidth / zoom.width,
              1
            ).toFixed(2) - 0.01;
          zoom.style.scale = zoom.scale;
          zoom.minscale = zoom.scale;
          zoom.style.transform = `translateZ(0)`;
          cover.appendChild(zoom);
          cover.classList.add("show");
          cover.focus();
        }
        function zoomImg(e) {
          e.preventDefault();
          if (
            (e.deltaY < 0 && zoom.scale > 4) ||
            (e.deltaY > 0 && zoom.scale < zoom.minscale)
          )
            return;
          zoom.scale *= e.deltaY < 0 ? 1.25 : 0.8;
          moveImg(e);
        }
        function moveImg(e) {
          let t,
            l,
            ih = zoom.clientHeight * zoom.scale,
            iw = zoom.clientWidth * zoom.scale,
            dh = docEl.clientHeight,
            dw = docEl.clientWidth;
          if (ih > dh) {
            t = -(ih - dh + 0.2 * dh) * (e.clientY / dh - 0.5);
          } else t = 0;
          if (iw > dw) {
            l = -(iw - dw + 0.2 * dw) * (e.clientX / dw - 0.5);
          } else l = 0;
          zoom.style.scale = zoom.scale;
          zoom.style.translate = `${l}px ${t}px 0px`;
        }
        function hideCover() {
          zoom.removeAttribute("style");
          zoom.removeAttribute("class");
          getEl("rep").replaceWith(zoom);
          cover.classList.remove("show");
        }
        function copyImg(e) {
          let img = e.target;
          if (img.tagName !== "IMG") return;
          let { canvas } = getThumb(img, 1920);
          canvas.toBlob((blob) =>
            navigator.clipboard.write([new ClipboardItem({ [blob.type]: blob })])
          );
          e.preventDefault();
        }
        function naviZoom(e) {
          e.stopPropagation();
          let index = getEl("rep").parentElement.index;
          index +=
            e.target === nextimg || e.key === "ArrowRight" || e.key === "d" ? 1 : -1;
          let wrap = getEl("img" + index);
          if (!wrap) return;
          if (!visImgs.has(index)) wrap.scrollIntoView();
          hideCover();
          toggleZoom({ target: wrap.children[0] });
        }
        async function getWH(file) {
          if (file.size < 30) return [0, 0];
          let view = new DataView(await file.slice(0, 30).arrayBuffer());
          let sign = view.getUint32();
          if (sign === 0x89504e47) return [view.getUint32(16), view.getUint32(20)];
          else if (sign === 0x47494638)
            return [view.getUint16(6, true), view.getUint16(8, true)];
          else if (sign >>> 16 === 0x424d)
            return [view.getInt32(18, true), view.getInt32(22, true)];
          else if (sign === 0x52494646) {
            let vp8 = view.getUint32(12);
            if (vp8 === 0x56503820)
              return [view.getUint16(26, true), view.getUint16(28, true)];
            else if (vp8 === 0x56503858)
              return [view.getUint24(24, true) + 1, view.getUint24(27, true) + 1];
            else if (vp8 === 0x5650384c) {
              return [
                (view.getUint16(21, true) & 0x3fff) + 1,
                ((view.getUint24(22, true) >>> 6) & 0x3fff) + 1,
              ];
            }
          } else if (sign >>> 8 === 0xffd8ff) {
            view = new DataView(await file.slice(0, 128 * 1024).arrayBuffer());
            let marker;
            let offset = 2;
            while (offset < view.byteLength) {
              marker = view.getUint16(offset);
              offset += 2;
              if (marker === 0xffc0 || marker === 0xffc2)
                return [view.getUint16(offset + 5), view.getUint16(offset + 3)];
              offset += view.getUint16(offset);
            }
          }
          let img = await new Promise((resolve) => {
            let img = new Image();
            let onloaded = () => {
              URL.revokeObjectURL(img.src);
              resolve(img);
            };
            img.onload = onloaded;
            img.onerror = onloaded;
            img.src = URL.createObjectURL(file);
          });
          return [img.naturalWidth, img.naturalHeight, img];
        }
        function parseRatio() {
          let arr = [
            [" ", ""],
            ["—", "-"],
            ["--", "-"],
            [":", ":"],
          ]
            .reduce((t, r) => t.replaceAll(...r), aspectratio.value)
            .split("-")
            .map((t, i) =>
              t === "" && i === 1
                ? Infinity
                : t
                    .split(":")
                    .map(Number)
                    .reduce((p, c) => p / c)
            )
            .sort();
          [minR, maxR] = arr.concat(arr);
          reflow();
        }
        initFlex();
        initSort();
        initFilt();
        initBackground();
        configs.forEach(initConfig);
        imgbox.onmouseout = addInfo;
        imgbox.onmouseenter = addInfo;
        cursorplace.onmouseout = addInfo;
        cover.onwheel = zoomImg;
        previmg.onclick = naviZoom;
        nextimg.onclick = naviZoom;
        cover.onmousemove = moveImg;
        cover.onclick = hideCover;
        resort.onclick = reflow;
        window.onresize = loadNext;
        imgbox.onclick = toggleZoom;
        document.onscroll = loadNext;
        document.ondragend = copyImg;
        document.ondrag = (e) => e.preventDefault();
        document.ondragover = (e) => e.preventDefault();
        document.ondragenter = (e) => e.preventDefault();
        pause.onclick = () => (loadingAll = 0);
        totop.onclick = () => docEl.scrollTo(0, 0);
        toend.onclick = () => docEl.scrollTo(0, docEl.scrollHeight);
        sidebtn.onclick = () => sidebar.classList.add("show");
        loadall.onclick = () => {
          loadingAll = 1;
          loadNext();
        };
        aspectratio.onkeydown = (e) => {
          if (e.key === "Enter") parseRatio();
        };
        treebtn.onclick = () => {
          treebar.classList.add("show");
          currdir?.scrollIntoView({ block: "center" });
        };
        jumpTo.onkeydown = (e) => {
          if (e.key === "Enter") getEl("img" + parseInt(jumpTo.value)).scrollIntoView();
        };
        colcountinput.addEventListener("change", () => {
          if (flextype === enums.colflex) reflow();
        });
        minheightinput.addEventListener("change", () => {
          if (flextype === enums.rowflex)
            requestAnimationFrame(() => {
              imgbox.querySelectorAll(".wrap").forEach(resize);
              loadNext();
            });
        });
        dirtree.onclick = (e) => {
          let li = e.target;
          if (li.tagName !== "LI") treebar.classList.remove("show");
          else
          // else if (sortby.value === enums.default && order.value === enums.asc)
            reflow(li.index - 1);
          e.stopPropagation();
        };
        dirtree.onwheel = (e) => {
          if (
            (dirtree.scrollTop === 0 && e.deltaY < 0) ||
            (dirtree.scrollTop + dirtree.clientHeight >= dirtree.scrollHeight &&
              e.deltaY > 0)
          )
            e.preventDefault();
        };
        document.addEventListener("mousedown", (e) => {
          if (e.button === 0) held = true;
        });
        document.addEventListener("mouseup", (e) => {
          if (e.button === 3) scrollBy(0, 0.9 * docEl.clientHeight);
          if (e.button === 4) scrollBy(0, -0.9 * docEl.clientHeight);
          if (e.button === 0) {
            held = false;
            indicator.classList.remove("show");
          }
          e.preventDefault();
        });
        document.addEventListener("scroll", () => {
          if (held) {
            let currIndex = Math.min(...visImgs);
            indicator.innerText = currIndex;
            indicator.classList.add("show");
          }
        });
        document.addEventListener(
          "click",
          (e) => {
            [sidebar, treebar].forEach((el) => {
              if (el.classList.contains("show") && !el.contains(e.target)) {
                el.classList.remove("show");
                e.stopPropagation();
              }
            });
          },
          true
        );
        document.addEventListener("keydown", (e) => {
          if (e.key === "Escape") {
            if (cover.classList.contains("show")) hideCover();
            else if (treebar.classList.contains("show"))
              treebar.classList.remove("show");
            else if (scrollAnimation) stopAutoScroll();
            else sidebar.classList.toggle("show");
          }
          if (
            cover.classList.contains("show") &&
            ["ArrowLeft", "ArrowRight", "a", "d"].includes(e.key)
          )
            naviZoom(e);
        });
        
        function startAutoScroll() {
          if (scrollAnimation) return;
          
          autoscroll.classList.add("active");
          docEl.style.cursor = "none";
          sidebar.classList.remove("show");
          
          const speed = scrollspeed.value * 0.5;
          let direction = 1;
          
          function scroll() {
            if (!scrollAnimation) return;
            
            const mode = scrollMode.value;
            const isAtBottom = docEl.scrollTop + docEl.clientHeight >= docEl.scrollHeight;
            const isAtTop = docEl.scrollTop <= 0;
            
            if (mode === "down") {
              if (isAtBottom) {
                docEl.scrollTo(0, 0);
              } else {
                docEl.scrollBy(0, speed);
              }
            } else if (mode === "updown") {
              if (isAtBottom) {
                direction = -1;
              } else if (isAtTop) {
                direction = 1;
              }
              docEl.scrollBy(0, speed * direction);
            }
            
            scrollAnimation = requestAnimationFrame(scroll);
          }
          
          scrollAnimation = requestAnimationFrame(scroll);
        }
        
        function stopAutoScroll() {
          if (scrollAnimation) {
            cancelAnimationFrame(scrollAnimation);
            scrollAnimation = null;
            autoscroll.classList.remove("active");
            docEl.style.cursor = "";
          }
        }
        
        autoscroll.onclick = () => {
          if (scrollAnimation) {
            stopAutoScroll();
          } else {
            startAutoScroll();
          }
        };
        
        scrollspeed.oninput = (e) => {
          speedvalue.innerText = e.target.value;
          if (scrollAnimation) {
            stopAutoScroll();
            startAutoScroll();
          }
        };
        
        document.addEventListener("visibilitychange", () => {
          if (document.hidden && scrollAnimation) {
            stopAutoScroll();
          }
        });
        
        scrollMode.onchange = () => {
          if (scrollAnimation) {
            stopAutoScroll();
            startAutoScroll();
          }
        };
        
    </script>
</body>

</html>
        
编辑器加载中
预览
控制台