找到10226个回复
  • @老虎会游泳,我好奇为何你的脚本会和图片浏览器(下称viewer冲突

    按说,viewerdefer,在DOMContentLoaded之前执行,(之前)你的在触发后执行,应该是可以由你保存好vieweronclick,最后存活的onclick是你的啊

  • @无名啊,现在我把插件代码改到底部了,我的代码改成立即执行表达式了,加载时也不会闪一下再消失了。

    请求确实发出了,但首先请求的是hu60.cn,那个302重定向给JS代码的执行和后续请求的取消保留了足够的时间,最终没有到file.hu60.cn的请求发出。

    图片.png(105.61 KB)

  • @无名啊,把插件放在网页底部,就可以取消事件监听器了。直接把代码暴露在最外层即可。这样就会在DOM解析好之后立即执行。
  • @无名啊,额,好像并非总是如此。有时候请求确实发出了,只是随后取消了。看起来必须把插件添加到网页底部,才能及时执行。

    image.png(189.05 KB)
  • @老虎会游泳,我装了你的,关了表情,测了本贴第一页,已缓存的图片算是发出请求吗?
    无标题.png.avif(43.59 KB)
  • @无名啊,为什么我强调“安装我的插件”,因为我可以保证我的插件里只有同步代码,所以执行时机一定是及时的。如果插件包含异步代码(async/await),那执行就不及时了,执行可能发生在多媒体内容开始加载之后,而非之前。

  • @无名啊,你的感觉可能不准确,你可以安装我的插件,关闭表情插件(因为表情插件有很多图片),然后自己看F12控制台。在用户点击前并没有图片请求发出。

  • @老虎会游泳,我感觉DOMContentLoaded触发后,各个图片都下了一小点了(所以也经常会闪烁一下才消失),也不知道能不能利用上

    那些拦截广告的,也不知道怎么做到不会有闪烁就过滤掉广告的,感觉像浏览器解析DOM前,就过滤掉了

  • @无名啊,至于不在file.hu60.cn上的文件,如果alt里没有大小,那在不请求服务器的情况下,自然没有任何办法得知其大小。
  • @老虎会游泳,噢,看漏了,看成“新上传的图片才有文件大小”了

    对外站图片用啥方法好呢

  • @无名啊所有位于file.hu60.cn的文件都可以使用

    function getSizeFromUrl(url) {
          url = hu60_decode_url64(url);
          var parts = url.match(/\/file\/(?:hash\/[^\/]+\/[a-f0-9]{32}|uuid\/[^\/]+\/[a-f0-9-]{36})(\d+)\b/);
          return parts ? parseInt(parts[1]) : 0;
        }
    

    获取文件大小。

    文件路径是这样生成的:

            if ($fileMd5 !== null) {
                $key = 'file/hash/' . $type . '/' . $fileMd5 . $fileSize . $ext;
            } else {
                $uuid = str::guidv4();
                $key = 'file/uuid/' . $type . '/' . $uuid . $fileSize . $ext;
            }
    
  • @老虎会游泳,旧图片、外站图片、音乐、视频啥的,好像没有提供文件大小

    对这些情况,有啥更好的办法吗(除了HEAD再查询一次)

  • @老虎会游泳,我用的我7楼的代码,有小概率能用上图片浏览器(此时正常,点击按钮重现图片后也正常),大部分时候被你的 footer.js 代替了,点击图片就新窗口打开

  • @无名啊有没有直接获取文件大小的方法,有,file.hu60.cn的文件大小在URL末尾。此外新上传的图片,alt里也会有文件大小。

        function getSizeFromUrl(url) {
          url = hu60_decode_url64(url);
          var parts = url.match(/\/file\/(?:hash\/[^\/]+\/[a-f0-9]{32}|uuid\/[^\/]+\/[a-f0-9-]{36})(\d+)\b/);
          return parts ? parseInt(parts[1]) : 0;
        }
        function getImgSize(img) {
          if (img.alt) {
            var parts = img.alt.match(/([0-9]+(:?\.[0-9]+)?\s*(?:[KMGT]B|bytes))/);
          	if (parts) return parts[1];
          }
          var size = getSizeFromUrl(img.src);
          return size > 0 ? humanize.filesize(size) : '';
        }
    
  • @无名啊,我猜viewer.js是通过addEventListener的方式添加点击事件的。

  • @老虎会游泳,我改写了下,主要更新内容:

    1. 按钮变得好看些
    2. 自动使图片、音乐、视频、内嵌HTML变成点击显示
    3. 自动检测目标链接文件大小。对于图片,若已缓存或较小(如 < 1MB),则直接显示

    感觉还存在以下问题:

    1. 不知道有没有直接获取文件大小的方法。

      目前是用fetchHEAD方法再发送一次请求,解析content-length判定文件大小。

      • 会不会有啥安全问题?
      • 会不会跨域啥的获取不了大小?
      • 会不会哪个服务器把HEADGET看,直接返回整个文件?
    2. 还是和图片浏览器插件冲突。

      按理说,我保留了原始img节点,onclick啥的应该都还保留着?

    @老虎会游泳,有空能指教下,问题出哪儿不?

    <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
    <script>
    
      const blocks = {
        userimg: {type: 'img', icon: 'image'},
        iframe_box: {type: 'iframe', icon: 'web'},
        audio_box: {type: 'audio', icon: 'audiotrack'},
        video_box: {type: 'video', icon: 'movie'},
      };
    
      function formatSize(size) {
        const i = size && Math.floor(Math.log(size) / Math.log(1024));
        return (size / (1 << (10 * i))).toFixed(i && 2) + ' ' + ' KMGT'[i].trim() + 'B';
      }
    
      customElements.define('my-button', class extends HTMLElement {
        #ref = {};
        #orig_node;
        #media_src;
        #media_node;
    
        #createElementFromObject(obj) {
          let el = document.createElement(obj.tag);
          for (const k in obj) {
            if (k === 'children')
              obj.children.forEach(c => el.appendChild(this.#createElementFromObject(c)));
            else if (k === 'text')
              el.textContent = obj[k];
            else if (k === 'ref')
              this.#ref[obj[k]] = el;
            else if (k !== 'tag')
              el.setAttribute(k, obj[k]);
          }
          return el;
        }
    
        constructor() {
          super();
    
          // 创建一个 shadow root
          let shadow = this.attachShadow({mode: 'open'});
    
          [
            {tag: 'link', rel: 'stylesheet', href: 'https://unpkg.com/material-components-web@latest/dist/material-components-web.min.css'},
            {tag: 'link', rel: 'stylesheet', href: 'https://fonts.googleapis.com/icon?family=Material+Icons'},
            {tag: 'script', src: 'https://unpkg.com/material-components-web@latest/dist/material-components-web.min.js'},
            {tag: 'button', class: 'mdc-fab mdc-fab--extended', style: 'margin: 10px', children: [
                {tag: 'div', class: 'mdc-fab__ripple'},
                {tag: 'span', class: 'material-icons mdc-fab__icon', ref: 'icon'},
                {tag: 'span', class: 'mdc-fab__label', ref: 'text'},
              ]},
            {tag: 'style', text: `
              :host {
                /* --mdc-theme-primary: #e74c3c; */
                --mdc-theme-secondary: #fafafa;
                /* --mdc-theme-background: #f00; */
                /* --mdc-theme-on-primary: #fff; */
                --mdc-theme-on-secondary: #2c3e50;
              }
            `},
          ].forEach(x => shadow.appendChild(this.#createElementFromObject(x)));
    
          this.onclick = this.recover;
        }
    
        get text() {
          return this.#ref.text.textContent;
        }
    
        set text(s) {
          this.#ref.text.textContent = s;
        }
    
        get icon() {
          return this.#ref.icon.textContent;
        }
    
        set icon(s) {
          this.#ref.icon.textContent = s;
        }
    
        // onclick() {  // 为什么不能这样重写方法??
        recover() {
          this.#media_node.src &&= this.#media_src;
          this.parentNode.replaceChild(this.#orig_node, this);
          delete this;
        }
    
        // 替换掉旧节点,展示本按钮
        replace(root_node, media_node) {
          this.#orig_node = root_node.parentNode.replaceChild(this, root_node);
          this.#media_node = media_node;
          this.#media_src = media_node.src;
          media_node.src &&= ' ';
    
          // 询问目标链接大小,太大则等待用户选择,较小直接显示
          if (this.#media_src) {
            fetch(this.#media_src, {method: 'HEAD'}).then(resp => {
              if (resp.ok) {
                const size = parseInt(resp.headers.get('content-length'));
                if (size < (1 << 20) && this.icon === blocks.userimg.icon)
                  this.recover();
                else if (size > 0)
                  this.text += `(${formatSize(size)})`;
              }
            });
          }
        }
      });
    
      document.addEventListener('DOMContentLoaded', () => {
        document.querySelectorAll(Object.keys(blocks).map(x => `.${x}`).join()).forEach(x => {
    
          // 获取 element 类型、显示按钮时的图标
          const {type, icon} = Object.entries(blocks).find(([k]) => x.classList.contains(k))[1];
    
          // 若图片已下载完毕,就让它显示吧
          if (type === 'img' && x.complete)
            return;
    
          // 获取真正的多媒体节点
          const media_node = ['audio', 'video'].includes(type) ? x.querySelector(type) : x;
    
          // 图片有外层链接,则准备替换整个外层
          if (type === 'img' && x.parentNode.href)
            x = x.parentNode;
    
          // 创建按钮
          let el = document.createElement('my-button');
          el.icon = icon;
          el.text = type === 'iframe' ? '内嵌网页' : media_node.alt || media_node.src;
          el.replace(x, media_node);
        });
      });
    </script>
    
  • @无名啊,冲突能解决吗
    一加8Pro

  • @大尨,小众插件

  • Screenshot_2022-07-30-19-38-27-651_com.quark.browser.jpg(458.02 KB)会和这个插件有冲突 https://hu60.cn/q.php/bbs.topic.103359.html @老虎会游泳
    Redmi K30 Pro
  • @无名啊,好吧 各有好处。