Skip to content

Suggestion to Integrate ViewerJS for Image Click-to-Zoom Functionality #15

@VinciYan

Description

@VinciYan

Hello, thank you for developing this excellent add-on. While using it, I noticed that images in cards cannot be conveniently viewed in full size. I temporarily integrated ViewerJS by modifying the template, enabling click-to-zoom functionality for images, and the experience has been great.

<!-- 先加载必要的CSS(ViewerJS核心样式) -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/viewerjs/1.11.7/viewer.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/css/font-awesome.min.css">
<script>
(function() {
// 全局变量只保留必要的
let viewer = null;
let isViewerActive = false;

// 增强的图片查找函数 - 覆盖正面所有图片
function findAllImages() {
  // 按优先级查找图片,确保覆盖正面所有图片
  const selectors = [
    'img', // 匹配所有图片(最高优先级)
    '#front-card-basic img',
    '.card img',
    '.content img',
    'body img:not([data-viewer-inited="true"])'
  ];
  
  // 去重收集所有图片
  const imagesSet = new Set();
  selectors.forEach(selector => {
    const elements = document.querySelectorAll(selector);
    elements.forEach(el => imagesSet.add(el));
  });
  
  const imagesArray = Array.from(imagesSet);
  console.log('Found total images (front template):', imagesArray.length);
  
  // 打印每个图片的信息,方便调试
  imagesArray.forEach((img, index) => {
    console.log(`Image ${index} (front):`, {
      src: img.src,
      id: img.id,
      className: img.className,
      parent: img.parentElement.tagName,
      container: img.closest('[id*="-card-"]')?.id || 'unknown'
    });
  });
  
  return imagesArray;
}

// 等待页面完全加载(包括图片)
function waitForCompleteLoad() {
  return new Promise(resolve => {
    // 如果页面已加载完成
    if (document.readyState === 'complete') {
      resolve();
    } else {
      // 等待所有资源加载完成
      const onLoad = () => {
        document.removeEventListener('DOMContentLoaded', onLoad);
        window.removeEventListener('load', onLoad);
        resolve();
      };
      document.addEventListener('DOMContentLoaded', onLoad);
      window.addEventListener('load', onLoad);
      
      // 超时保护:1秒后强制解析
      setTimeout(resolve, 1000);
    }
  });
}

// 加载ViewerJS
function loadViewerJS() {
  return new Promise((resolve, reject) => {
    if (window.Viewer) {
      resolve(window.Viewer);
      return;
    }
    
    const script = document.createElement('script');
    script.src = 'https://cdnjs.cloudflare.com/ajax/libs/viewerjs/1.11.7/viewer.min.js';
    script.onload = () => resolve(window.Viewer);
    script.onerror = (e) => reject(e);
    document.head.appendChild(script);
  });
}

// 初始化图片查看器
async function initImageViewer() {
  try {
    // 等待页面完全加载(包括图片)
    await waitForCompleteLoad();
    console.log('Page fully loaded (front template), starting viewer init');
    
    // 加载ViewerJS
    await loadViewerJS();
    console.log('ViewerJS loaded successfully (front template)');
    
    // 查找所有图片(增强版)
    const images = findAllImages();
    
    if (images.length === 0) {
      console.warn('No images found in front template');
      // 尝试延迟再查找一次
      setTimeout(() => {
        const retryImages = findAllImages();
        if (retryImages.length > 0) {
          setupImageClickHandlers(retryImages);
        }
      }, 500);
      return;
    }
    
    // 为图片绑定点击事件
    setupImageClickHandlers(images);
    
  } catch (error) {
    console.error('Failed to initialize viewer (front template):', error);
  }
}

// 为图片绑定点击事件
function setupImageClickHandlers(images) {
  images.forEach(img => {
    // 避免重复处理
    if (img.dataset.viewerInited) return;
    img.dataset.viewerInited = 'true';
    
    // 设置基础样式
    img.style.cursor = 'zoom-in';
    img.style.maxWidth = '100%';
    img.style.height = 'auto';
    
    console.log('Binding click handler to image (front):', img.src);
    
    // 点击图片打开查看器
    img.addEventListener('click', function(e) {
      e.preventDefault();
      e.stopPropagation();
      console.log('Image clicked (front), opening viewer:', this.src);
      openViewer(this);
    });
  });
  
  console.log('Viewer initialized for', images.length, 'images (front template)');
}

// 打开查看器(修复了点击图片显示不正确的问题)
function openViewer(targetImg) {
  // 如果已有查看器,先销毁
  if (viewer) {
    try {
      viewer.destroy();
    } catch (e) {
      console.warn('Error destroying old viewer (front):', e);
    }
  }
  
  // 收集所有已初始化的图片
  const imageElements = Array.from(document.querySelectorAll('img[data-viewer-inited="true"]'));
  
  // 找到当前图片的索引
  const startIndex = imageElements.indexOf(targetImg);
  console.log('Opening viewer for image index (front):', startIndex, 'out of', imageElements.length);
  
  if (startIndex === -1) {
    console.error('Target image not found in imageElements');
    return;
  }
  
  // 创建一个临时的图片容器,仅用于ViewerJS初始化
  const tempContainer = document.createElement('div');
  tempContainer.style.display = 'none';
  tempContainer.id = 'viewer-temp-container';
  
  // 为临时容器添加所有图片
  imageElements.forEach((img, index) => {
    const imgClone = img.cloneNode(true);
    imgClone.style.display = 'block';
    imgClone.style.width = '100%';
    imgClone.style.height = 'auto';
    tempContainer.appendChild(imgClone);
  });
  
  document.body.appendChild(tempContainer);
  
  // 创建Viewer实例,使用临时容器中的图片
  viewer = new Viewer(tempContainer, {
    button: true,
    toolbar: {
      zoomIn: 1,
      zoomOut: 1,
      oneToOne: 1,
      reset: 1,
      prev: 1,
      play: 0,
      next: 1,
      rotateLeft: 1,
      rotateRight: 1,
      flipHorizontal: 1,
      flipVertical: 1,
    },
    navbar: true,
    title: true,
    keyboard: true,
    movable: true,
    zoomable: true,
    rotatable: true,
    scalable: true,
    fullscreen: true,
    loop: true,
    inline: false,
    shown: function() {
      console.log('Viewer opened successfully (front template)');
      isViewerActive = true;
      
      // 立即跳转到点击的图片
      if (startIndex >= 0) {
        viewer.view(startIndex);
      }
      
      // 确保关闭按钮可见
      setTimeout(() => {
        const closeBtn = document.querySelector('.viewer-button.viewer-close');
        if (closeBtn) {
          closeBtn.style.display = 'block';
          closeBtn.style.opacity = '1';
          closeBtn.style.zIndex = '999999';
          closeBtn.style.pointerEvents = 'auto';
        }
      }, 100);
    },
    hidden: function() {
      console.log('Viewer closed (front template)');
      isViewerActive = false;
      
      // 清理临时容器和实例
      setTimeout(() => {
        try {
          if (tempContainer && tempContainer.parentNode) {
            tempContainer.parentNode.removeChild(tempContainer);
          }
          viewer.destroy();
          viewer = null;
        } catch (e) {
          console.warn('Error cleaning up viewer (front):', e);
        }
      }, 200);
    }
  });
  
  // 显示查看器
  viewer.show();
}

// 添加必要的样式修复(包含移动端优化)
function addFixStyles() {
  const style = document.createElement('style');
  style.textContent = `
/* 确保ViewerJS样式正常显示 */
.viewer-container { z-index: 999999 !important; }
.viewer-backdrop { z-index: 999998 !important; }

/* 修复关闭按钮样式 */
.viewer-button {
  display: block !important;
  visibility: visible !important;
  opacity: 1 !important;
  z-index: 1000000 !important;
  pointer-events: auto !important;
}

/* 修复工具栏样式 */
.viewer-toolbar {
  z-index: 1000001 !important;
  margin-bottom: 20px !important;
}

.viewer-toolbar ul {
  display: flex !important;
  justify-content: center !important;
  padding: 10px !important;
  background: rgba(0,0,0,0.5) !important;
  border-radius: 10px !important;
  margin: 0 auto !important;
}

.viewer-toolbar li {
  margin: 0 5px !important;
  width: 44px !important;
  height: 44px !important;
  border-radius: 50% !important;
  background: rgba(255,255,255,0.1) !important;
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
  cursor: pointer !important;
  pointer-events: auto !important;
}

.viewer-toolbar li:hover {
  background: rgba(255,255,255,0.3) !important;
}

/* 确保图片可点击 */
img[data-viewer-inited="true"] {
  pointer-events: auto !important;
}

/* 移动端适配 */
@media (max-width: 768px) {
  .viewer-toolbar li {
    width: 40px !important;
    height: 40px !important;
  }
  
  /* 移动端优化正面图片显示 */
  #front-card-basic img {
    max-width: 100% !important;
    height: auto !important;
    display: block !important;
    margin: 0 auto !important;
  }
}

/* 确保正面字段的图片样式正常 */
#front-card-basic img {
  max-width: 100%;
  height: auto;
  margin: 10px 0;
}
`;
  document.head.appendChild(style);
}

// 初始化
addFixStyles();

// 延迟初始化,确保DOM完全加载
setTimeout(initImageViewer, 300);

// 页面卸载时清理
window.addEventListener('beforeunload', () => {
  if (viewer) {
    try {
      viewer.destroy();
    } catch (e) {}
  }
});
})();
</script>

<div id="front-card-basic">
{{Front}}
</div>
<div id="tags-card">
{{Tags}}
</div>
<div id="difficulty-card">
{{Difficulty}}
</div>

<!-- 加载其他资源(保留原有逻辑) -->
<script>
(function() {
  var jsLocal = '_better_markdown_anki.js';
  var jsCDN = 'https://cdn.jsdelivr.net/gh/alexthillen/[email protected]/better-markdown-anki/dist/_better_markdown_anki.js';
  var cssLocal = '_better_markdown_anki.css';
  var cssCDN = 'https://cdn.jsdelivr.net/gh/alexthillen/[email protected]/better-markdown-anki/dist/_better_markdown_anki.css';
  var cssUser = '_better_markdown_anki_user_style.css';

  function loadCSS(href, fallback) {
    var link = document.createElement('link');
    link.rel = 'stylesheet';
    link.href = href;
    link.onerror = function() {
      if (fallback) {
        var fallbackLink = document.createElement('link');
        fallbackLink.rel = 'stylesheet';
        fallbackLink.href = fallback;
        document.head.appendChild(fallbackLink);
      }
    };
    document.head.appendChild(link);
  }

  function loadCSSSilent(href) {
    var link = document.createElement('link');
    link.rel = 'stylesheet';
    link.href = href;
    link.onerror = function() {
      console.log('CSS failed to load:', href);
    };
    document.head.appendChild(link);
  }

  function loadJS(src, fallback) {
    var scriptSelector = 'script[src="' + src + '"]';
    if (!document.querySelector(scriptSelector)) {
      var script = document.createElement('script');
      script.type = 'module'; // Essential for ES modules
      script.src = src;
      script.onerror = function() {
        if (fallback && !document.querySelector('script[src="' + fallback + '"]')) {
          var fallbackScript = document.createElement('script');
          fallbackScript.type = 'module';
          fallbackScript.src = fallback;
          document.head.appendChild(fallbackScript);
        }
      };
      document.head.appendChild(script);
    }
  }

  loadCSS(cssLocal, cssCDN);
  loadCSSSilent(cssUser);
  loadJS(jsLocal, jsCDN);
})();
</script>
<!-- prettier-ignore -->
<!-- format-ignore -->
<div id="front-card-basic">
{{Front}}
</div>
<div id="back-card-basic">
{{Back}}
</div>
<div id="extra-card-basic">
{{Extra}}
</div>
<div id="tags-card">
{{Tags}}
</div>
<div id="difficulty-card">
{{Difficulty}}
</div>

<!-- 先加载必要的CSS(ViewerJS核心样式) -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/viewerjs/1.11.7/viewer.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/css/font-awesome.min.css">
<script>
(function() {
// 全局变量只保留必要的
let viewer = null;
let isViewerActive = false;

// 增强的图片查找函数 - 覆盖所有卡片区域的图片
function findAllImages() {
  // 按优先级查找图片,包含正面、背面、额外字段的所有图片
  const selectors = [
    'img', // 匹配所有图片(最高优先级)
    '#front-card-basic img',
    '#back-card-basic img',
    '#extra-card-basic img',
    '.card img',
    '.content img',
    'body img:not([data-viewer-inited="true"])'
  ];
  
  // 去重收集所有图片
  const imagesSet = new Set();
  selectors.forEach(selector => {
    const elements = document.querySelectorAll(selector);
    elements.forEach(el => imagesSet.add(el));
  });
  
  const imagesArray = Array.from(imagesSet);
  console.log('Found total images (back template):', imagesArray.length);
  
  // 打印每个图片的信息,方便调试
  imagesArray.forEach((img, index) => {
    console.log(`Image ${index} (back):`, {
      src: img.src,
      id: img.id,
      className: img.className,
      parent: img.parentElement.tagName,
      container: img.closest('[id*="-card-"]')?.id || 'unknown'
    });
  });
  
  return imagesArray;
}

// 等待页面完全加载(包括图片)
function waitForCompleteLoad() {
  return new Promise(resolve => {
    // 如果页面已加载完成
    if (document.readyState === 'complete') {
      resolve();
    } else {
      // 等待所有资源加载完成
      const onLoad = () => {
        document.removeEventListener('DOMContentLoaded', onLoad);
        window.removeEventListener('load', onLoad);
        resolve();
      };
      document.addEventListener('DOMContentLoaded', onLoad);
      window.addEventListener('load', onLoad);
      
      // 超时保护:1秒后强制解析
      setTimeout(resolve, 1000);
    }
  });
}

// 加载ViewerJS
function loadViewerJS() {
  return new Promise((resolve, reject) => {
    if (window.Viewer) {
      resolve(window.Viewer);
      return;
    }
    
    const script = document.createElement('script');
    script.src = 'https://cdnjs.cloudflare.com/ajax/libs/viewerjs/1.11.7/viewer.min.js';
    script.onload = () => resolve(window.Viewer);
    script.onerror = (e) => reject(e);
    document.head.appendChild(script);
  });
}

// 初始化图片查看器
async function initImageViewer() {
  try {
    // 等待页面完全加载(包括图片)
    await waitForCompleteLoad();
    console.log('Page fully loaded (back template), starting viewer init');
    
    // 加载ViewerJS
    await loadViewerJS();
    console.log('ViewerJS loaded successfully (back template)');
    
    // 查找所有图片(增强版)
    const images = findAllImages();
    
    if (images.length === 0) {
      console.warn('No images found in back template');
      // 尝试延迟再查找一次
      setTimeout(() => {
        const retryImages = findAllImages();
        if (retryImages.length > 0) {
          setupImageClickHandlers(retryImages);
        }
      }, 500);
      return;
    }
    
    // 为图片绑定点击事件
    setupImageClickHandlers(images);
    
  } catch (error) {
    console.error('Failed to initialize viewer (back template):', error);
  }
}

// 为图片绑定点击事件
function setupImageClickHandlers(images) {
  images.forEach(img => {
    // 避免重复处理
    if (img.dataset.viewerInited) return;
    img.dataset.viewerInited = 'true';
    
    // 设置基础样式
    img.style.cursor = 'zoom-in';
    img.style.maxWidth = '100%';
    img.style.height = 'auto';
    
    console.log('Binding click handler to image (back):', img.src);
    
    // 点击图片打开查看器
    img.addEventListener('click', function(e) {
      e.preventDefault();
      e.stopPropagation();
      console.log('Image clicked (back), opening viewer:', this.src);
      openViewer(this);
    });
  });
  
  console.log('Viewer initialized for', images.length, 'images (back template)');
}

// 打开查看器(修复了点击图片显示不正确的问题)
function openViewer(targetImg) {
  // 如果已有查看器,先销毁
  if (viewer) {
    try {
      viewer.destroy();
    } catch (e) {
      console.warn('Error destroying old viewer (back):', e);
    }
  }
  
  // 收集所有已初始化的图片
  const imageElements = Array.from(document.querySelectorAll('img[data-viewer-inited="true"]'));
  
  // 找到当前图片的索引
  const startIndex = imageElements.indexOf(targetImg);
  console.log('Opening viewer for image index (back):', startIndex, 'out of', imageElements.length);
  
  if (startIndex === -1) {
    console.error('Target image not found in imageElements');
    return;
  }
  
  // 创建一个临时的图片容器,仅用于ViewerJS初始化
  const tempContainer = document.createElement('div');
  tempContainer.style.display = 'none';
  tempContainer.id = 'viewer-temp-container';
  
  // 为临时容器添加所有图片
  imageElements.forEach((img, index) => {
    const imgClone = img.cloneNode(true);
    imgClone.style.display = 'block';
    imgClone.style.width = '100%';
    imgClone.style.height = 'auto';
    tempContainer.appendChild(imgClone);
  });
  
  document.body.appendChild(tempContainer);
  
  // 创建Viewer实例,使用临时容器中的图片
  viewer = new Viewer(tempContainer, {
    button: true,
    toolbar: {
      zoomIn: 1,
      zoomOut: 1,
      oneToOne: 1,
      reset: 1,
      prev: 1,
      play: 0,
      next: 1,
      rotateLeft: 1,
      rotateRight: 1,
      flipHorizontal: 1,
      flipVertical: 1,
    },
    navbar: true,
    title: true,
    keyboard: true,
    movable: true,
    zoomable: true,
    rotatable: true,
    scalable: true,
    fullscreen: true,
    loop: true,
    inline: false,
    shown: function() {
      console.log('Viewer opened successfully (back template)');
      isViewerActive = true;
      
      // 立即跳转到点击的图片
      if (startIndex >= 0) {
        viewer.view(startIndex);
      }
      
      // 确保关闭按钮可见
      setTimeout(() => {
        const closeBtn = document.querySelector('.viewer-button.viewer-close');
        if (closeBtn) {
          closeBtn.style.display = 'block';
          closeBtn.style.opacity = '1';
          closeBtn.style.zIndex = '999999';
          closeBtn.style.pointerEvents = 'auto';
        }
      }, 100);
    },
    hidden: function() {
      console.log('Viewer closed (back template)');
      isViewerActive = false;
      
      // 清理临时容器和实例
      setTimeout(() => {
        try {
          if (tempContainer && tempContainer.parentNode) {
            tempContainer.parentNode.removeChild(tempContainer);
          }
          viewer.destroy();
          viewer = null;
        } catch (e) {
          console.warn('Error cleaning up viewer (back):', e);
        }
      }, 200);
    }
  });
  
  // 显示查看器
  viewer.show();
}

// 添加必要的样式修复
function addFixStyles() {
  const style = document.createElement('style');
  style.textContent = `
/* 确保ViewerJS样式正常显示 */
.viewer-container { z-index: 999999 !important; }
.viewer-backdrop { z-index: 999998 !important; }

/* 修复关闭按钮样式 */
.viewer-button {
  display: block !important;
  visibility: visible !important;
  opacity: 1 !important;
  z-index: 1000000 !important;
  pointer-events: auto !important;
}

/* 修复工具栏样式 */
.viewer-toolbar {
  z-index: 1000001 !important;
  margin-bottom: 20px !important;
}

.viewer-toolbar ul {
  display: flex !important;
  justify-content: center !important;
  padding: 10px !important;
  background: rgba(0,0,0,0.5) !important;
  border-radius: 10px !important;
  margin: 0 auto !important;
}

.viewer-toolbar li {
  margin: 0 5px !important;
  width: 44px !important;
  height: 44px !important;
  border-radius: 50% !important;
  background: rgba(255,255,255,0.1) !important;
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
  cursor: pointer !important;
  pointer-events: auto !important;
}

.viewer-toolbar li:hover {
  background: rgba(255,255,255,0.3) !important;
}

/* 确保图片可点击 */
img[data-viewer-inited="true"] {
  pointer-events: auto !important;
}

/* 移动端适配 */
@media (max-width: 768px) {
  .viewer-toolbar li {
    width: 40px !important;
    height: 40px !important;
  }
  
  /* 移动端优化图片显示 */
  #back-card-basic img, #extra-card-basic img {
    max-width: 100% !important;
    height: auto !important;
    display: block !important;
    margin: 0 auto !important;
  }
}

/* 确保背面和额外字段的图片样式正常 */
#back-card-basic img, #extra-card-basic img {
  max-width: 100%;
  height: auto;
  margin: 10px 0;
}
`;
  document.head.appendChild(style);
}

// 初始化
addFixStyles();

// 延迟初始化,确保DOM完全加载(背面模板延迟稍长,确保内容加载)
setTimeout(initImageViewer, 500);

// 页面卸载时清理
window.addEventListener('beforeunload', () => {
  if (viewer) {
    try {
      viewer.destroy();
    } catch (e) {}
  }
});
})();
</script>

<!-- 加载其他资源(保留原有逻辑) -->
<script>
(function() {
  var jsLocal = '_better_markdown_anki.js';
  var jsCDN = 'https://cdn.jsdelivr.net/gh/alexthillen/[email protected]/better-markdown-anki/dist/_better_markdown_anki.js';
  var cssLocal = '_better_markdown_anki.css';
  var cssCDN = 'https://cdn.jsdelivr.net/gh/alexthillen/[email protected]/better-markdown-anki/dist/_better_markdown_anki.css';
  var cssUser = '_better_markdown_anki_user_style.css';

  function loadCSS(href, fallback) {
    var link = document.createElement('link');
    link.rel = 'stylesheet';
    link.href = href;
    link.onerror = function() {
      if (fallback) {
        var fallbackLink = document.createElement('link');
        fallbackLink.rel = 'stylesheet';
        fallbackLink.href = fallback;
        document.head.appendChild(fallbackLink);
      }
    };
    document.head.appendChild(link);
  }

  function loadCSSSilent(href) {
    var link = document.createElement('link');
    link.rel = 'stylesheet';
    link.href = href;
    link.onerror = function() {
      console.log('CSS failed to load:', href);
    };
    document.head.appendChild(link);
  }

  function loadJS(src, fallback) {
    var scriptSelector = 'script[src="' + src + '"]';
    if (!document.querySelector(scriptSelector)) {
      var script = document.createElement('script');
      script.type = 'module'; // Essential for ES modules
      script.src = src;
      script.onerror = function() {
        if (fallback && !document.querySelector('script[src="' + fallback + '"]')) {
          var fallbackScript = document.createElement('script');
          fallbackScript.type = 'module';
          fallbackScript.src = fallback;
          document.head.appendChild(fallbackScript);
        }
      };
      document.head.appendChild(script);
    }
  }

  loadCSS(cssLocal, cssCDN);
  loadCSSSilent(cssUser);
  loadJS(jsLocal, jsCDN);
})();
</script>

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions