SVG from Zero to Senior · Part 15 — Interactive Maps with Pan & Zoom
The grand finale: drive the viewBox with the mouse to build pan and zoom-toward-cursor navigation — the engine behind every web map. Drag-to-pan math, the zoom-to-point formula, non-scaling strokes, and choropleth regions. With a live map.
Ta kết thúc ở nơi bắt đầu — với viewBox từ Phần 1 — và biến nó thành tương tác đã tay nhất cả series. Một bản đồ pan và zoom được. Phần đẹp: các hình không hề di chuyển. Ta chỉ trượt và đổi kích thước cửa sổ máy ảnh. Làm chủ cái này là con hiểu cỗ máy sau Google Maps, mọi sơ đồ mặt bằng SVG, và mọi diagram zoom được.
Kéo để pan, lăn để zoom về con trỏ, click một vùng.
Mở demo đầy đủ:
Ý tưởng cốt lõi: di cửa sổ, không di thế giới
Nhớ Phần 1: viewBox là cửa sổ máy ảnh trên bản vẽ. Vậy:
- Pan = đổi
min-x/min-y. - Zoom = đổi
width/height(nhỏ hơn = zoom vào).
Ta giữ một object JS vb = { x, y, w, h }, biến đổi nó khi có input, và ghi lại:
function apply() {
svg.setAttribute('viewBox', `${vb.x} ${vb.y} ${vb.w} ${vb.h}`);
}
Không transform, không render lại hình — một thuộc tính làm tất cả.
Pixel màn hình → user unit
Bản đồ hiển thị ở kích thước pixel CSS nào đó, nhưng mọi thứ tính trong user unit của viewBox. Nên ta đổi vị trí chuột từ pixel sang user space, tính theo viewBox hiện tại:
function toUser(e) {
const rect = svg.getBoundingClientRect();
const px = e.clientX - rect.left;
const py = e.clientY - rect.top;
return {
x: vb.x + (px / rect.width) * vb.w,
y: vb.y + (py / rect.height) * vb.h,
};
}
(Ở Phần 9 ta dùng getScreenCTM().inverse() — cũng được; bản tường minh này làm toán viewBox lộ ra.)
Pan bằng kéo
Khi kéo, trừ chuyển động con trỏ (theo user unit) khỏi vị trí cửa sổ:
function onMove(e) {
const u = toUser(e);
vb.x -= u.x - last.x; // dragging right moves the window left
vb.y -= u.y - last.y;
apply();
last = toUser(e); // re-read in the NEW viewBox to stay 1:1
}
Điểm tinh tế: sau khi di cửa sổ, đọc lại last trong viewBox đã cập nhật để nội dung bám con trỏ chính xác, không trôi.
Zoom về phía con trỏ (phần hay)
Zoom rẻ tiền chỉ scale w/h quanh góc trên-trái — cảm giác sai, nội dung bay đi. Zoom tốt giữ điểm dưới con trỏ đứng yên. Công thức:
function zoomAt(factor, ux, uy) { // ux,uy = cursor in user units
vb.w *= factor;
vb.h *= factor;
// pin (ux,uy): its fractional position in the window must stay constant
vb.x = ux - (ux - vb.x) * factor;
vb.y = uy - (uy - vb.y) * factor;
apply();
}
factor < 1 zoom vào, > 1 zoom ra. Nối nó với wheel (đổi deltaY thành factor) và nút +/− (zoom quanh tâm). Giới hạn w/h trong khoảng min/max để người dùng không zoom vô hạn hay lạc mất bản đồ.
Hai chi tiết production
vector-effect="non-scaling-stroke"trên viền vùng giữ chúng độ rộng pixel không đổi dù zoom — nếu không viền phình khi zoom vào.- Phân biệt click vs kéo. Một cú kéo kết thúc trên một vùng không nên kích hoạt “click vùng”. Theo dõi xem con trỏ có di chuyển quá ngưỡng nhỏ không và bỏ qua click nếu có.
Choropleth: vùng tô theo dữ liệu
Bản đồ thật là tập các vùng <path>/<polygon> (export từ GeoJSON qua một phép chiếu như d3-geo). Tô mỗi vùng theo một giá trị dữ liệu — dân số, doanh thu, nhiệt độ — và con có một choropleth:
region.style.fill = colorScale(data[region.id]); // value → color
Kết hợp với cỗ máy pan/zoom và handler click từng vùng (uỷ quyền sự kiện từ Phần 9) và con đã dựng một bản đồ dữ liệu tương tác thật — không cần thư viện bản đồ.
Lời cảnh báo của sư phụ
- Ghim con trỏ khi zoom, nếu không bản đồ cảm giác hỏng. Công thức theo
factorlà toàn bộ bí quyết. non-scaling-strokecho viền để line không béo lên khi zoom.- Tách click khỏi kéo bằng ngưỡng di chuyển, nếu không mỗi lần pan kết thúc bằng một lựa chọn ngoài ý muốn.
- Giới hạn khoảng zoom để người dùng không lạc.
Luyện tập, không thì coi như chưa học
- Pan + zoom con lăn: cài object
vbvà công thứczoomAttrên một cảnh SVG bất kỳ. - Zoom-về-vùng: khi click vùng, animate
viewBoxvề bounding box của vùng đó (getBBox()). - Choropleth nhỏ: tô năm vùng từ một mảng dữ liệu và thêm chú giải.
Hành trình, thực sự hoàn tất
Mười lăm phần trước con chưa giải thích nổi viewBox. Giờ con đã dùng nó dựng một cỗ máy bản đồ — và trên đường làm chủ hình, path, sơn, chữ, transform, animation, filter, mask, scripting, nghề production, morphing, đồng hồ, art sinh tạo, và tích hợp framework. Đó là, không phóng đại, trình độ điều khiển SVG cấp senior. Giờ đi vẽ web sắc nét hơn. Sư phụ cúi chào, lần này là thật.