<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="w-full max-w-4xl bg-white rounded-xl shadow-lg p-6">
<div class="text-center mb-6">
<h1 class="text-3xl font-bold text-gray-900">Trình xem ảnh trên Canvas</h1>
<p class="text-gray-600 mt-2">Sử dụng con lăn chuột để phóng to/thu nhỏ. Nhấn và kéo chuột để di chuyển ảnh.</p>
</div>
<!-- Vùng chứa Canvas -->
<div class="mb-4 bg-gray-200 rounded-lg overflow-hidden">
<canvas id="imageCanvas"></canvas>
</div>
<!-- Bảng điều khiển -->
<div class="flex flex-col sm:flex-row items-center justify-center gap-4">
<!-- Nút tải ảnh lên -->
<div class="custom-file-button">
<label for="imageLoader" class="cursor-pointer bg-blue-600 text-white font-semibold py-2 px-5 rounded-lg hover:bg-blue-700 transition-colors duration-300 shadow-sm">
Tải ảnh lên
</label>
<input type="file" id="imageLoader" name="imageLoader" accept="image/*"/>
</div>
<!-- Các nút điều khiển zoom -->
<div class="flex items-center gap-2">
<button id="zoomInBtn" class="w-10 h-10 flex items-center justify-center bg-gray-200 rounded-full hover:bg-gray-300 transition-colors duration-200 text-lg font-bold">-</button>
<button id="resetBtn" class="bg-gray-200 text-gray-800 font-semibold py-2 px-5 rounded-lg hover:bg-gray-300 transition-colors duration-300">Reset</button>
<button id="zoomOutBtn" class="w-10 h-10 flex items-center justify-center bg-gray-200 rounded-full hover:bg-gray-300 transition-colors duration-200 text-lg font-bold">+</button>
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
body {
font-family: 'Inter', sans-serif;
}
canvas {
cursor: grab;
border: 2px solid #e2e8f0;
border-radius: 0.5rem;
}
canvas:active {
cursor: grabbing;
}
.custom-file-button input[type="file"] {
display: none;
}
// Lấy các phần tử DOM
const canvas = document.getElementById('imageCanvas');
const ctx = canvas.getContext('2d');
const imageLoader = document.getElementById('imageLoader');
const zoomInBtn = document.getElementById('zoomInBtn');
const zoomOutBtn = document.getElementById('zoomOutBtn');
const resetBtn = document.getElementById('resetBtn');
// Khởi tạo ảnh và các biến trạng thái
const img = new Image();
let scale = 1;
let offsetX = 0;
let offsetY = 0;
let isDragging = false;
let lastX = 0;
let lastY = 0;
const ZOOM_SENSITIVITY = 0.001;
const BUTTON_ZOOM_FACTOR = 1.2;
// Thiết lập kích thước canvas theo container
function resizeCanvas() {
const container = canvas.parentElement;
canvas.width = container.clientWidth;
canvas.height = window.innerHeight * 0.6; // Chiếm 60% chiều cao cửa sổ
draw(); // Vẽ lại khi thay đổi kích thước
}
// Hàm vẽ chính
function draw() {
// Xóa toàn bộ canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Lưu trạng thái context hiện tại
ctx.save();
// Di chuyển gốc tọa độ đến vị trí pan
ctx.translate(offsetX, offsetY);
// Phóng to/thu nhỏ từ gốc tọa độ mới
ctx.scale(scale, scale);
// Tính toán vị trí để vẽ ảnh sao cho nó căn giữa
const x = (canvas.width / scale - img.width) / 2;
const y = (canvas.height / scale - img.height) / 2;
// Vẽ ảnh
ctx.drawImage(img, x, y);
// Khôi phục lại trạng thái context
ctx.restore();
}
// Xử lý khi tải ảnh xong
img.onload = () => {
resetTransformations();
};
// Hàm reset về trạng thái ban đầu
function resetTransformations() {
const canvasAspect = canvas.width / canvas.height;
const imageAspect = img.width / img.height;
if (imageAspect > canvasAspect) {
scale = canvas.width / img.width;
} else {
scale = canvas.height / img.height;
}
offsetX = (canvas.width - img.width * scale) / 2;
offsetY = (canvas.height - img.height * scale) / 2;
draw();
}
// Xử lý khi người dùng chọn file
imageLoader.addEventListener('change', (e) => {
const reader = new FileReader();
reader.onload = (event) => {
img.src = event.target.result;
}
if(e.target.files[0]) {
reader.readAsDataURL(e.target.files[0]);
}
});
// Xử lý sự kiện chuột
canvas.addEventListener('mousedown', (e) => {
isDragging = true;
canvas.style.cursor = 'grabbing';
lastX = e.clientX;
lastY = e.clientY;
});
canvas.addEventListener('mouseup', () => {
isDragging = false;
canvas.style.cursor = 'grab';
});
canvas.addEventListener('mouseleave', () => {
isDragging = false;
canvas.style.cursor = 'grab';
});
canvas.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const dx = e.clientX - lastX;
const dy = e.clientY - lastY;
offsetX += dx;
offsetY += dy;
lastX = e.clientX;
lastY = e.clientY;
draw();
});
// Xử lý con lăn chuột để zoom
canvas.addEventListener('wheel', (e) => {
e.preventDefault(); // Ngăn trang cuộn
const rect = canvas.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
// Tính toán vị trí chuột trên ảnh trước khi zoom
const mouseBeforeZoomX = (mouseX - offsetX) / scale;
const mouseBeforeZoomY = (mouseY - offsetY) / scale;
// Cập nhật scale
const delta = e.deltaY * ZOOM_SENSITIVITY * -1;
const newScale = scale * Math.exp(delta);
scale = Math.max(0.1, Math.min(newScale, 20)); // Giới hạn mức zoom
// Cập nhật offset để giữ nguyên vị trí chuột trên ảnh
offsetX = mouseX - mouseBeforeZoomX * scale;
offsetY = mouseY - mouseBeforeZoomY * scale;
draw();
});
// Hàm zoom với tâm là trung tâm canvas
function zoom(factor) {
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
// Tính toán vị trí chuột trên ảnh trước khi zoom
const centerBeforeZoomX = (centerX - offsetX) / scale;
const centerBeforeZoomY = (centerY - offsetY) / scale;
// Cập nhật scale
const newScale = scale * factor;
scale = Math.max(0.1, Math.min(newScale, 20)); // Giới hạn mức zoom
// Cập nhật offset để giữ nguyên vị trí chuột trên ảnh
offsetX = centerX - centerBeforeZoomX * scale;
offsetY = centerY - centerBeforeZoomY * scale;
draw();
}
// Sự kiện cho các nút điều khiển
zoomInBtn.addEventListener('click', () => zoom(1 / BUTTON_ZOOM_FACTOR));
zoomOutBtn.addEventListener('click', () => zoom(BUTTON_ZOOM_FACTOR));
resetBtn.addEventListener('click', resetTransformations);
// Khởi tạo
window.addEventListener('resize', resizeCanvas);
// Tải ảnh mặc định khi bắt đầu
img.src = 'https://placehold.co/1200x800/e2e8f0/333333?text=Tải+ảnh+của+bạn';
// Thiết lập kích thước canvas lần đầu
resizeCanvas();