Trước khi dùng trình tạo video UGC miễn phí, không cần đăng ký: kiểm tra ảnh bằng File API
Hai ảnh được đặt vào hai ô CSS có cùng chiều rộng nên trông rất cân đối. Nhưng một tệp có thể lớn gấp đôi tệp kia; hoặc cả hai cùng kích thước mà vị trí chủ thể đã đổi hoàn toàn. Với frontend và QA, đây là lúc cần đọc thông tin thật từ tệp thay vì suy luận từ hình thu nhỏ.
Bài này xây dựng một trang HTML duy nhất: chọn ảnh đầu và ảnh cuối, xem kích thước trình duyệt báo, so kích thước và tỷ lệ riêng, xử lý tệp lỗi và đổi ảnh nhanh. Công cụ chạy cục bộ, không cần thư viện hay máy chủ. Phần nhận xét nội dung được giữ riêng để các con số không biến thành kết luận “hai ảnh tương thích”.

Đọc đúng đại lượng trước khi viết điều kiện File API cho phép trang đọc các tệp người dùng chủ động chọn. Trong ví dụ, File.size là số byte; sau khi ảnh được giải mã, naturalWidth và naturalHeight cung cấp kích thước nội tại mà trình duyệt báo.
Theo tài liệu naturalWidth, đây là kích thước đã hiệu chỉnh mật độ, tính bằng CSS pixel. Nó không phải chiều rộng ô xem trước, cũng không phải cam kết về số pixel được mã hóa trong mọi tệp. Trình duyệt có thể can thiệp vào ảnh trước khi hiển thị; ví dụ này không phải trình phân tích metadata chuyên dụng.
Tách hai câu hỏi số học:
Kích thước có giống nhau không? So cả chiều rộng lẫn chiều cao. Tỷ lệ có giống nhau không? So tích chéo w1 * h2 và w2 * h1, thay vì so chuỗi tỷ lệ đã làm tròn để hiển thị. Với hai ảnh trình duyệt báo 640×360 và 1280×720, tỷ lệ giống nhau nhưng kích thước khác. Hai ảnh 640×360 có thể lại chứa hai bố cục khác nhau. Không nhánh nào trong hai phép so này hiểu được chủ thể của ảnh.
Thuộc tính accept chỉ gợi ý cho hộp chọn tệp. Đuôi tệp, File.type hay một lần giải mã thành công không chứng nhận tệp an toàn. Mã dưới đây gợi ý PNG/JPEG/WebP nhưng vẫn xử lý lỗi từ trình duyệt; nó không thay thế kiểm tra ở máy chủ của một hệ thống nhận tải lên.
Một tệp HTML có thể lưu và mở trực tiếp Sao chép toàn bộ khối sau vào tệp UTF-8 tên frame-preflight.html, rồi mở bằng trình duyệt. Bấm nhãn của từng ô để chọn ảnh; dùng Tab để tới điều khiển chọn tệp và nút xóa. Chọn các ảnh nhỏ do bạn sở hữu hoặc được phép dùng khi thử mã lần đầu.
<!doctype html>
<html lang="vi"> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Kiểm tra hai ảnh cục bộ</title> <style> * { box-sizing: border-box; } body { margin: 0; background: #f6f2e9; color: #20394d; font: 16px/1.55 system-ui, sans-serif; } main { max-width: 1120px; margin: auto; padding: 28px; } h1 { line-height: 1.2; font-size: clamp(26px, 4vw, 38px); } .grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px; } section { min-width: 0; padding: 20px; background: white; border: 1px solid #c7d1d7; border-radius: 12px; } label { display: inline-block; font-weight: 700; padding: 8px 12px; border: 1px solid #8398a6; border-radius: 6px; cursor: pointer; } input { position: absolute; width: 1px; height: 1px; clip-path: inset(50%); overflow: hidden; } section:focus-within { outline: 3px solid #ae7200; outline-offset: 2px; } .info { white-space: pre-line; overflow-wrap: anywhere; min-height: 84px; } .preview { min-height: 210px; display: grid; place-items: center; background: #f6f7f7; } .preview img { display: block; max-width: 100%; max-height: 290px; width: auto; height: auto; } #comparison { padding: 16px; background: #e7eef2; white-space: pre-line; border-radius: 8px; } button { padding: 10px 18px; font: inherit; cursor: pointer; } :focus-visible { outline: 3px solid #ae7200; outline-offset: 3px; } small { display: block; } @media (max-width: 650px) { .grid { grid-template-columns: 1fr; } main { padding: 16px; } } </style> <main> <small>FILE API · DEMO CỤC BỘ</small>Kiểm tra hai ảnh cục bộ
Chọn ảnh đầu và ảnh cuối. Trang này không có máy chủ, thư viện hay chức năng tải tệp lên.
Cùng kích thước hoặc tỷ lệ không chứng minh nội dung nhất quán. Hãy tự xem chủ thể, góc nhìn, vùng cắt, ánh sáng và các mốc cố định.
<button id="reset" type="button">Xóa cả hai</button><small>Kích thước là naturalWidth/naturalHeight do trình duyệt báo, đã hiệu chỉnh mật độ, tính bằng CSS pixel. accept và MIME chỉ là gợi ý; đây không phải kiểm tra an toàn tệp hay bảo đảm dịch vụ tạo video sẽ nhận ảnh.</small>
</main> <script> const slots = ['start', 'end'].map(id => ({ input: document.getElementById(id), info: document.getElementById(id + '-info'), preview: document.getElementById(id + '-preview'), sequence: 0, url: null, size: null })); const comparison = document.getElementById('comparison');function compare() { const [a, b] = slots.map(slot => slot.size); if (!a || !b) { comparison.textContent = 'Chưa đủ hai ảnh đã giải mã để so sánh.'; return; } const sameSize = a[0] === b[0] && a[1] === b[1]; const sameRatio = a[0] * b[1] === b[0] * a[1]; comparison.textContent = 'Kích thước: ' + (sameSize ? 'giống' : 'khác') + '.\nTỷ lệ: ' + (sameRatio ? 'giống' : 'khác') + '.'; }
function clearSlot(slot) { ++slot.sequence; // Vô hiệu hóa công việc cũ còn chờ. slot.preview.replaceChildren(); if (slot.url) URL.revokeObjectURL(slot.url); slot.url = null; slot.size = null; slot.info.dataset.state = 'empty'; slot.info.textContent = 'Chưa chọn ảnh.'; compare(); }
async function selectFile(slot) { clearSlot(slot); const ticket = slot.sequence; const file = slot.input.files[0]; if (!file) return; slot.info.dataset.state = 'loading'; slot.info.textContent = 'Đang giải mã: ' + file.name; try { if (file.size === 0) throw new Error('Tệp rỗng.'); slot.url = URL.createObjectURL(file); const image = new Image(); image.src = slot.url; await image.decode(); if (ticket !== slot.sequence) return; const w = image.naturalWidth, h = image.naturalHeight; if (!(w > 0 && h > 0)) throw new Error('Không có kích thước.'); slot.size = [w, h]; image.alt = slot.input.id === 'start' ? 'Ảnh đầu đã chọn' : 'Ảnh cuối đã chọn'; slot.preview.replaceChildren(image); slot.info.dataset.state = 'ready'; slot.info.textContent = file.name + '\n' + file.size + ' byte · ' + w + ' × ' + h + ' CSS px\nTỷ lệ: ' + (w / h).toFixed(4); // Giữ URL khi ảnh còn hiển thị; clearSlot sẽ thu hồi nó. } catch (error) { if (ticket !== slot.sequence) return; if (slot.url) URL.revokeObjectURL(slot.url); slot.url = null; slot.size = null; slot.info.dataset.state = 'error'; slot.info.textContent = 'Không đọc được ảnh: ' + file.name + '. Tệp có thể rỗng, hỏng hoặc không được trình duyệt hỗ trợ.'; } compare(); }
for (const slot of slots) { clearSlot(slot); slot.input.addEventListener('change', () => selectFile(slot)); } document.getElementById('reset').addEventListener('click', () => { for (const slot of slots) { slot.input.value = ''; clearSlot(slot); } }); window.addEventListener('pagehide', () => { for (const slot of slots) { slot.input.value = ''; clearSlot(slot); } }); </script>
</html> Ảnh chụp dưới đây được lấy từ chính trang này sau khi chạy với hai PNG kiểm thử cục bộ. Các hình vòm và khối vàng được vẽ bằng Canvas trong bộ kiểm thử, không phải kết quả của mô hình tạo video.
Ba quyết định giúp trang không giữ kết quả sai Chỉ công việc của lần chọn mới nhất được cập nhật giao diện image.decode() trả về Promise; ảnh có thể giải mã xong sau khi người dùng đã đổi tệp hoặc bấm xóa. Mỗi ô vì vậy có một sequence. clearSlot() tăng số này, còn công việc đang chạy giữ bản chụp ticket của nó.
Sau await, nếu hai số không còn bằng nhau, kết quả bị bỏ qua. Kiểm tra tương tự trong catch tránh tình huống lỗi của ảnh cũ xóa trạng thái ảnh mới. Cơ chế này không cần giả định lần giải mã nào hoàn tất trước.
Giữ URL khi ảnh còn hiển thị, thu hồi khi không còn dùng Một object URL được tạo cho mỗi lần chọn tệp có dữ liệu. Khi ảnh đã hiển thị, mã giữ URL đó để ảnh vẫn có thể được tương tác. Khi thay ảnh, xóa, rời trang hoặc gặp lỗi, mã gọi URL.revokeObjectURL() cho URL tương ứng.
Thu hồi tham chiếu là một trách nhiệm vòng đời cụ thể; nó không phải kết quả đo heap hay bảo đảm bộ nhớ luôn nhỏ. Đoạn mã cũng không hủy được mọi công việc giải mã đã bắt đầu: sequence ngăn kết quả cũ quay lại giao diện.
Xóa số liệu cũ trước khi bắt đầu đọc ảnh mới clearSlot() đặt size về null, gỡ ảnh xem trước và tính lại trạng thái so sánh ngay. Vì thế một tệp hỏng không được hưởng số liệu của ảnh hợp lệ trước đó. Tên tệp và thông báo dùng textContent, nên tên trông giống thẻ HTML vẫn chỉ là chữ.
Những trường hợp đã chạy trên mã này
Bộ kiểm thử cục bộ dùng Playwright và Chrome 155.0.8059.85. Phiên bản mã được chụp ảnh và đưa nguyên vẹn ở trên đã đạt 12/12 trường hợp sau; đây là kiểm thử trang HTML, không phải thực nghiệm UGC Maker AI.
Trường hợp Quan sát trong lần chạy Chưa chọn hoặc chỉ chọn một ô Chưa đủ hai ảnh để so sánh Hai PNG 640×360 nhưng đổi chỗ hình vòm và khối Báo kích thước, tỷ lệ giống; vẫn giữ lời nhắc xem nội dung PNG 640×360 và 1280×720 Kích thước khác, tỷ lệ giống PNG 640×360 và 400×400 Tỷ lệ khác Thay ảnh hợp lệ bằng dữ liệu hỏng mang tên .png Báo lỗi, không giữ ảnh hoặc kích thước cũ, URL được thu hồi Tệp 0 byte Báo lỗi trước khi tạo object URL Xóa khi cả hai ảnh đã sẵn sàng Hai ảnh và các URL đang giữ được xóa Kích hoạt nhánh pagehide Xóa lựa chọn tệp và tài nguyên xem trước Lần đọc cũ hoàn tất sau lần chọn mới Chỉ ảnh mới được hiển thị Xóa khi lần đọc cũ còn chờ Ảnh cũ không xuất hiện trở lại Trình duyệt báo chiều rộng nội tại bằng 0 Đi vào nhánh lỗi và thu hồi URL Tên tệp chứa cú pháp HTML, MIME để trống Tên được hiển thị như văn bản; PNG thật vẫn được giải mã Hai tình huống đổi nhanh/xóa dùng bộ kiểm thử giữ lại phần tiếp tục của Promise sau khi giải mã PNG thật, rồi cho nó chạy muộn để kiểm soát thứ tự. Nhánh chiều rộng bằng 0 dùng getter mô phỏng; đó không phải một ảnh thực đã giải mã thành công với chiều rộng 0. Nhánh pagehide được kích hoạt bằng sự kiện trong kiểm thử, chưa phải khảo sát đầy đủ hành vi khôi phục trang của mọi trình duyệt.
Trong lần chạy này không quan sát thấy yêu cầu HTTP(S) từ các trang demo. Bản thân mã không có fetch, thư viện ngoài hoặc chức năng gửi tệp. Điều đó không chứng nhận mọi trình duyệt, tiện ích mở rộng hay môi trường đều không phát sinh mạng. Các mẫu số học dùng PNG; chưa có tuyên bố bao phủ mọi định dạng, EXIF, trình duyệt, hiệu năng, bộ nhớ hay bảo mật.
Muốn tự kiểm tra nhanh mà không dùng bộ tự động, hãy tạo một ảnh 640×360, một bản phóng lên 1280×720 và một ảnh vuông. Lần lượt chọn các cặp, thay một ô bằng tệp văn bản đổi tên .png, rồi xóa. Đối chiếu từng trạng thái hiển thị; đừng lấy hình thu nhỏ cùng cỡ làm kết quả mong đợi.
Sau các con số, xem lại những mốc cần giữ Đặt hai ảnh cạnh nhau và xác định điều bạn muốn thay đổi. Trong một cảnh hình học, mốc có thể là chân vòm, đường nền và cạnh của khối vàng. Nếu chỉ muốn khối trượt một đoạn, hãy xem vòm, đường nền, vùng cắt và góc nhìn có thực sự giữ nguyên không.
Với ảnh khác, tự chọn các mốc phù hợp: vị trí chủ thể, chi tiết thiết kế, ánh sáng, nền và khung hình. Công cụ không nhận diện hay chấm điểm những mốc này. Khi hai ảnh mô tả hai cảnh khác nhau, hãy quyết định sửa ảnh hay dùng một lần cắt trong phần mềm dựng, thay vì diễn giải “cùng tỷ lệ” thành “một chuyển động liên tục”.
Khi đã chọn được hướng, mới dùng bước tạo video thủ công Gợi ý công cụ: UGC Maker AI là một lựa chọn để tạo đoạn video minh họa được mô tả trong bài viết này. Trang công cụ chuyên biệt mô tả quyền dùng miễn phí, không cần đăng ký và dùng cụm powered by MiniMax H3. Trang UGC Maker AI — MiniMax H3 miễn phí còn có nhãn unlimited. Các mô tả này thuộc công cụ miễn phí đó; không suy ra cam kết về thời lượng, độ phân giải, ưu tiên hàng đợi, số tác vụ đồng thời hay toàn bộ mô hình trả phí.
Hướng dẫn công cụ trên trình duyệt cho phép dùng prompts dạng văn bản hoặc cung cấp cả ảnh đầu lẫn ảnh cuối. Trang tiền kiểm trong bài không kết nối API hay chuyển tệp sang dịch vụ: nếu chọn cách dùng hai ảnh, bạn tự đưa hai tệp đã chuẩn bị vào hai vị trí tương ứng. Không dùng ảnh ghép so sánh làm cả hai đầu vào.
Một prompt minh họa có thể là: “Giữ nguyên vòm xanh, đường nền, góc nhìn và ánh sáng; chỉ để khối vàng trượt nhẹ sang phải giữa hai trạng thái đã cung cấp. Không thêm chữ, vật thể hoặc đổi cảnh.” Đây là ý định để kiểm tra, không phải kết quả đã đạt được.
Sau khi tạo, xem toàn bộ bản xem trước và chỉ tải về phiên bản phù hợp với ý định của bạn. Bài này không chạy một lượt tạo video hay xác nhận mô hình giữ đúng các mốc. Giá trị của trang cục bộ nằm ở chỗ nó tách rõ số liệu đọc được, trạng thái lỗi và phần đánh giá nội dung cần con người thực hiện.
All rights reserved