iT邦幫忙

2025 iThome 鐵人賽

DAY 22
0

Day 22: 容器尺寸問題的深度診斷與修復 - 從雙重縮放到精確置中

前言

在 Day 21 中,我們添加了即時尺寸監控功能來解決佈局問題。然而,在實際使用過程中,我們發現了更多深層的容器尺寸問題,特別是「觀看全地圖」功能和節點置中功能。今天,我們將深入探討這些問題,並提供完整的解決方案。

問題發現:雙重縮放問題

問題描述

當使用者點擊「觀看全地圖」按鈕時,發現地圖變得非常小,幾乎看不見。透過瀏覽器開發者工具檢查,發現 zoomInner 容器的尺寸被設定為:

<div id="zoomInner" style="width: 4632px; height: 2763px; transform: translate(...)">

問題分析

這個問題的根源是雙重縮放

  1. 第一層縮放zoomInner 被設定為原始 SVG 尺寸(4632x2763px)
  2. 第二層縮放:透過 transform: scale() 進行縮放

這導致了以下問題:

  • 容器本身已經很大
  • 再加上 scale() 縮放,讓圖片變得過小
  • 使用者無法看到完整的地圖

解決方案

我們移除了 zoomInner 的固定尺寸設定:

// 修改前:設定固定尺寸
const finalWidth = Math.min(origW, maxReasonableWidth);
const finalHeight = Math.min(origH, maxReasonableHeight);
zoomInner.style.width = finalWidth + "px";
zoomInner.style.height = finalHeight + "px";

// 修改後:使用自動尺寸
zoomInner.style.width = "auto";
zoomInner.style.height = "auto";

背景圖片層級問題

問題描述

在修復縮放問題後,我們發現背景圖片被拖曳和縮放,這不是預期的行為。背景圖片應該保持固定位置。

問題分析

背景圖片原本設定在 #zoomInner::before 偽元素中,這會導致:

  • 背景圖片隨著地圖一起被拖曳
  • 背景圖片隨著地圖一起被縮放
  • 使用者體驗不佳

解決方案

將背景圖片移動到 #graph 容器層級:

#graph {
  background-image: url("世界地圖背景圖片");
  background-repeat: no-repeat;
  background-position: center 60%; /* 向下移動,避免被 header 擋住 */
  background-size: cover;
  background-attachment: fixed; /* 固定背景,不受拖曳影響 */
}

並添加淡化效果:

background-image: 
  linear-gradient(rgba(250, 248, 243, 0.85), rgba(250, 248, 243, 0.85)),
  url("背景圖片");

節點置中功能的深度診斷

問題描述

在實作搜尋功能時,我們希望使用者點擊搜尋結果後,該節點能自動置中在螢幕中央。然而,置中功能完全失效,節點要麼跑到螢幕外,要麼位置不正確。

座標系統分析

這是一個複雜的多層座標系統問題:

  1. SVG 內部座標:節點的 getBBox() 返回相對於 SVG 原點的座標
  2. zoomInner 容器:包含整個 SVG,有自己的 transform
  3. 螢幕座標:最終顯示在螢幕上的位置

診斷工具:詳細 Log

為了診斷問題,我們添加了詳細的 log 系統:

function centerNode(nodeId, targetScale = 150) {
  console.log('=== 開始置中節點 ===');
  console.log('目標節點 ID:', nodeId);
  console.log('目標縮放比例:', targetScale);
  
  // 找到指定節點
  const nodeDetail = state.nodeDetails.find(n => n.id === nodeId);
  if (!nodeDetail) {
    console.log('❌ 找不到節點詳細資訊:', nodeId);
    console.log('可用的節點:', state.nodeDetails.map(n => n.id));
    return false;
  }
  console.log('✅ 找到節點詳細資訊:', nodeDetail);
  
  // ... 更多詳細 log
}

邊界限制問題

在診斷過程中,我們發現 applyDragTransform 函數中的邊界限制過於嚴格:

// 問題:邊界限制太嚴格
const maxTranslateX = Math.max(0, (scaledWidth - containerWidth) / 2 + containerWidth * 0.5);
const minTranslateX = Math.min(0, -(scaledWidth - containerWidth) / 2 - containerWidth * 0.5);

// 解決:放寬邊界限制
const maxTranslateX = Math.max(0, (scaledWidth - containerWidth) / 2 + containerWidth * 2);
const minTranslateX = Math.min(0, -(scaledWidth - containerWidth) / 2 - containerWidth * 2);

座標轉換修正

正確的座標轉換邏輯:

// 1. 取得節點在 SVG 中的位置
const bbox = targetNodeElement.getBBox();
const nodeX = bbox.x + bbox.width / 2;
const nodeY = bbox.y + bbox.height / 2;

// 2. 計算縮放後的位置
const scale = state.scalePct / 100;
const scaledNodeX = nodeX * scale;
const scaledNodeY = nodeY * scale;

// 3. 計算需要移動的距離
const moveX = containerCenterX - scaledNodeX;
const moveY = containerCenterY - scaledNodeY;

// 4. 應用位移
state.currentTranslateX = moveX;
state.currentTranslateY = moveY;

Debug 模式的優化

問題:過度 Log 輸出

在 Debug 模式中,我們發現每秒都會輸出大量的容器尺寸資訊,污染控制台。

解決方案

移除詳細的 console.log 輸出,只保留必要的資訊更新:

// 修改前:大量 console.log 輸出
console.log('=== 容器尺寸詳細資訊 ===');
console.log('clientWidth:', graph.clientWidth);
console.log('clientHeight:', graph.clientHeight);
// ... 更多 log

// 修改後:移除詳細 log
// 移除詳細的 console.log 輸出,避免每秒輸出大量資訊

驗證系統的建立

置中後驗證

為了確保置中功能正常工作,我們建立了驗證系統:

// 驗證最終位置
setTimeout(() => {
  const finalBbox = targetNodeElement.getBBox();
  const finalNodeX = finalBbox.x + finalBbox.width / 2;
  const finalNodeY = finalBbox.y + finalBbox.height / 2;
  const finalScale = state.scalePct / 100;
  
  // 計算節點在螢幕上的實際位置
  const finalScreenX = (finalNodeX * finalScale) + state.currentTranslateX;
  const finalScreenY = (finalNodeY * finalScale) + state.currentTranslateY;
  
  const finalContainerCenterX = graph.clientWidth / 2;
  const finalContainerCenterY = graph.clientHeight / 2;
  
  console.log('=== 置中後驗證 ===');
  console.log('節點在螢幕上的實際位置:', finalScreenX, finalScreenY);
  console.log('容器中心:', finalContainerCenterX, finalContainerCenterY);
  console.log('偏差:', Math.abs(finalScreenX - finalContainerCenterX), Math.abs(finalScreenY - finalContainerCenterY));
}, 100);

技術要點總結

1. 避免雙重縮放

  • 不要同時設定容器尺寸和 transform: scale()
  • 使用自動尺寸讓容器根據內容調整

2. 正確的座標轉換

  • 理解多層座標系統的關係
  • 正確計算節點在螢幕上的實際位置
  • 考慮縮放和位移的影響

3. 邊界限制的平衡

  • 提供足夠的移動範圍
  • 防止地圖跑到看不見的地方
  • 根據實際需求調整限制

4. 診斷工具的重要性

  • 詳細的 log 幫助快速定位問題
  • 驗證系統確保功能正常
  • 視覺回饋提升開發效率

未來改進方向

1. 效能優化

  • 減少不必要的 DOM 操作
  • 優化縮放和拖曳的效能
  • 使用 requestAnimationFrame 優化動畫

2. 使用者體驗

  • 添加平滑的動畫效果
  • 提供更多視覺回饋
  • 優化觸控設備的支援

3. 功能擴展

  • 支援多個節點同時置中
  • 添加路徑動畫效果
  • 提供更多自訂選項

結語

今天的開發過程讓我們深刻理解了容器尺寸管理的複雜性。從雙重縮放問題到精確的節點置中,每一個問題都需要仔細的診斷和系統性的解決方案。

透過建立完善的診斷工具和驗證系統,我們不僅解決了當前的問題,也為未來的開發奠定了堅實的基礎。這種系統性的問題解決方法,正是專業開發者必備的技能。

在 AI 時代,我們需要的不只是快速解決問題,更要建立可維護、可擴展的系統架構。今天的經驗,將成為我們在 Google Apps Script 開發路上的寶貴財富。


上一篇
Day 21: 開發者工具實戰 - 添加即時尺寸監控功能來解決佈局問題
下一篇
Day 23: 介面美學革命 - 從現代化到古樸風格的視覺轉型
系列文
亨利羊帶你 Google Apps Script 從入門到精通:放棄長篇大論的教學吧,你需要的只是一些精心設計的 prompt!23
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言