Hi mọi người! Lại là mình đây. Hồi trước, mình hay dùng Imgur, Google Drive, hay Dropbox để up ảnh lấy link nhúng vào Blogspot, diễn đàn, hay mạng xã hội. Nhưng mà, mấy dịch vụ này giờ siết chặt: Imgur hạn chế hotlink, Google Drive thì rườm rà, Dropbox thì link hay die. Mình đang đau đầu tìm cách thay thế, cho đến khi đọc được bài của Anh Trai Nắng về cách dùng Cloudinary để upload ảnh. Nhờ ý tưởng đó, mình đã phát triển công cụ upload ảnh lấy link siêu tiện cho Blogspot, và hôm nay mình sẽ chia sẻ lại với anh em! Cảm ơn Anh Trai Nắng vì đã truyền cảm hứng để mình mày mò ra cách này.

Cloudinary là gì? Vì sao dùng nó để up ảnh lấy link?

Cloudinary là dịch vụ lưu trữ ảnh và video chuyên nghiệp, cho phép upload trực tiếp từ trình duyệt và nhận link chuẩn HTTPS. Ưu điểm: tốc độ nhanh, link bền vững, hỗ trợ tối ưu f_auto,q_auto
để ảnh vừa nhẹ vừa nét. Dùng gói free thôi cũng dư sức cho nhu cầu blog cá nhân hay website học tập. Đây là lý do nhiều anh em IT chọn Cloudinary để thay thế Imgur, Google Drive hay Dropbox khi cần up ảnh lấy link.
Chuẩn bị tài khoản Cloudinary trước khi bắt tay vào code
Để code chạy được, anh em cần tạo tài khoản Cloudinary. Làm vài bước là xong:
- Đăng ký Cloudinary (gói miễn phí).
Lấy thông tin Cloud Name - Vào Dashboard, lấy Cloud Name.
- Trong phần Settings → Upload → Upload Presets, tạo một preset dạng Unsigned.
- Lưu lại tên preset đó.
Tạo trang up ảnh lấy link nhanh
Sau khi có Cloud Name và Upload Preset, chúng ta tiến hành dựng ngay trang upload ảnh. Cách làm đơn giản: tạo một Page mới trong Blogger, chuyển sang chế độ HTML và dán đoạn code dưới đây. Trang này sẽ có khung kéo-thả, nút copy link, và cả preview ảnh đã upload.
<div class="up-i">
<div class="dropzone">
<div class="infoimg" id="svgZ">
<svg enable-background="new 0 0 256 256" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg"><g><g><g>
<path d="M127.7,31.6c-7.2,0.9-18.7,4.6-25.3,8.2c-13.6,7.6-26.2,21.9-31.1,35.5L70,79h-4.1c-5.6,0-15.1,2-20.8,4.4
c-15.1,6.3-27.5,19.8-32.8,35.6c-1.9,5.7-2.1,7.3-2.2,17.9c0,10.8,0.2,12.1,2.2,17.9c6.5,18.7,20.7,32.2,39.6,37.8
c5.1,1.6,8,1.7,27.9,2c21.9,0.4,22.3,0.4,24-1.2c2.3-2.3,2.2-6.6-0.1-8.2c-1.5-1.1-4.9-1.2-21.2-1.2c-22.2,0-26.7-0.7-36-5.1
c-4.7-2.3-7.4-4.3-12.2-9.2c-9.6-9.8-13.6-19.4-13.6-32.7c0-13.5,3.9-22.9,13.7-32.8c9.9-10.1,20.1-14.2,35.2-14.2
c3.7,0,7.2-0.3,7.7-0.6c0.5-0.4,2-3.5,3.3-7.1C84.4,71,91,61.6,99.9,54.5c24.5-19.1,58.7-16.2,79.7,6.8c9,9.8,13.9,21.1,15,34.6
c0.8,8.7,1.5,9.6,8.6,10.9c13.7,2.4,25.8,13,30.3,26.4c1.8,5.3,2.2,15.2,0.8,21.1c-2.1,9.2-10.7,20.3-19.2,24.8
c-8.1,4.3-12,4.8-38,4.8c-21.6,0-23.6,0.1-25.2,1.4c-2.3,1.9-2.3,6.2,0.1,8.1c1.5,1.2,3.9,1.4,24.8,1.4c17.3,0,24.7-0.3,29.3-1.2
c18.4-3.5,34.4-18.7,38.7-36.9c1.6-6.5,1.6-16.9,0-23.3c-3.9-16.6-18-31.3-34.5-36.1l-4.4-1.2l-1.2-7.2
C198.5,52.5,164.8,27.5,127.7,31.6z"></path>
<path d="M126.3,134c-0.6,0.2-7.9,7.1-16.1,15.3c-12.9,12.8-15,15.2-15,17.2c0,1.2,0.3,2.7,0.6,3.2c1,1.6,4.1,2.7,5.8,2.3
c0.9-0.3,6-4.8,11.3-10l9.5-9.5v34.4c0,32.1,0.1,34.4,1.4,36.1c2,2.4,6.1,2.4,8.1,0c1.3-1.7,1.4-4,1.4-36.1v-34.4l9.6,9.5
c5.2,5.1,10.3,9.6,11.2,10c1.7,0.5,4.8-0.7,5.8-2.3c0.3-0.5,0.6-1.9,0.6-3.2c0-2-2.1-4.4-14.9-17.3c-8.2-8.2-15.7-15-16.6-15.2
C128.2,133.8,126.9,133.8,126.3,134z"></path></g></g></g>
</svg>
<p><b>Chọn file ảnh</b> hoặc kéo ảnh vào đây (nhiều file)</p>
</div>
<input accept="image/*" class="input" type="file" multiple />
</div>
<div class="actions hidden" aria-live="polite">
<button type="button" class="btn btn-primary copy-all-btn" disabled>Copy tất cả link</button>
<button type="button" class="btn btn-ghost clear-list-btn" disabled>Xoá danh sách</button>
</div>
<div class="results"></div>
<div id="toast" class="toast"></div>
</div>
<div class="loading-modal"><div class="loading-spinner"></div></div>
<style>
.up-i { background: var(--contentBg-alt); padding: 10px; border-radius: 5px; }
.dropzone { border: 1px dashed #999; position: relative; margin: 0 auto; clear: both; }
.dropzone.dropzone-dragging { border-color: #000; }
#svgZ { text-align:center; }
#svgZ svg { width:79px; height:79px; }
.input { height: 100%; left: 0; outline: 0; opacity: 0; position: absolute; top: 0; width: 100%; cursor: cell; }
.actions { display:flex; gap:10px; justify-content:center; margin:12px 0; }
.actions.hidden { display:none; }
.copy-all-btn:disabled, .clear-list-btn:disabled { opacity:.6; cursor:not-allowed; }
.actions .btn { all:unset; display:inline-flex; align-items:center; justify-content:center; padding:8px 12px; border-radius:8px; line-height:1; border:1px solid transparent; cursor:pointer; user-select:none; -webkit-user-select:none; }
.actions .btn[disabled] { opacity:.5; cursor:not-allowed; }
.actions .btn-primary { background: var(--linkB, #2563eb); color:#fff; }
.actions .btn-primary:hover { filter:brightness(.95); }
.actions .btn-ghost { background:#f3f4f6; color:#111827; border-color:#e5e7eb; }
.actions .btn-ghost:hover { background:#e5e7eb; }
.results { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 14px; }
.card { border:1px solid var(--bodyCa); border-radius:10px; padding:10px; background:#fff; display:flex; flex-direction:column; }
.thumb { display:flex; align-items:center; justify-content:center; min-height:160px; }
.thumb img { max-width:100%; max-height:240px; height:auto; border-radius:6px; }
.link-row { display:flex; gap:8px; margin-top:auto; align-items:center; }
.image-url { flex:1; min-width:0; background:transparent; color:#08102b; border:1px solid var(--bodyCa); padding:6px 8px; border-radius:6px; }
.copy-btn { padding: 6px 10px; background: var(--linkB); color:#fff; border:none; border-radius:6px; cursor:pointer; }
.toast { position: fixed; top: 30px; right: 30px; background-color: #22c55e; color: white; padding: 12px 24px; border-radius: 6px; font-size: 14px; opacity: 0; pointer-events: none; transition: opacity 0.4s ease, transform 0.4s ease; z-index: 9999; box-shadow: 0 6px 24px rgba(0,0,0,.12); transform: translateY(0); }
.toast.error { background-color:#ef4444; }
.toast.show { opacity:1; transform: translateY(4px); }
.toast.warn { background-color:#f59e0b; }
body.loading .loading-modal { display:flex; }
.loading-modal { background-color: rgba(255,255,255,.8); display:none; position:fixed; z-index:1000; inset:0; justify-content:center; align-items:center; }
.loading-spinner { border:6px solid #f3f3f3; border-top:6px solid #3f51b5; border-radius:50%; width:50px; height:50px; animation: spin 1s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
</style>
<script>
const CLOUD_NAME = "domvpkjum";
const UPLOAD_PRESET = "truongdevs";
const ACCEPT_TYPES = ["image/jpeg", "image/png", "image/gif"];
const MAX_SIZE = 5 * 1024 * 1024;
const MAX_FILES_PER_BATCH = 20;
function showToast(message, variant = "success") {
const toast = document.getElementById("toast");
toast.textContent = message;
toast.classList.remove("error", "warn");
if (variant === "error") toast.classList.add("error");
if (variant === "warn") toast.classList.add("warn");
toast.classList.add("show");
setTimeout(() => {
toast.classList.remove("show", "error", "warn");
}, 2500);
}
function createLoading() {
if (!document.querySelector(".loading-modal")) {
const div = document.createElement("div");
div.className = "loading-modal";
const spinner = document.createElement("div");
spinner.className = "loading-spinner";
div.appendChild(spinner);
document.body.appendChild(div);
}
}
async function uploadToCloudinary(file) {
const endpoint = `https://api.cloudinary.com/v1_1/${CLOUD_NAME}/image/upload`;
const formData = new FormData();
formData.append("file", file);
formData.append("upload_preset", UPLOAD_PRESET);
const res = await fetch(endpoint, { method: "POST", body: formData });
if (!res.ok) {
const text = await res.text();
throw new Error(text || (res.status + " " + res.statusText));
}
return await res.json();
}
function optimizedUrl(secureUrl) {
return secureUrl.replace("/upload/", "/upload/f_auto,q_auto/");
}
function copyText(text) {
return navigator.clipboard.writeText(text)
.then(() => showToast("Đã sao chép!"))
.catch(() => showToast("Không thể sao chép tự động.", "error"));
}
function clampFiles(files) {
const arr = Array.from(files);
if (arr.length > MAX_FILES_PER_BATCH) {
showToast(
`Chỉ xử lý tối đa ${MAX_FILES_PER_BATCH} ảnh/lần. Đã bỏ qua ${arr.length - MAX_FILES_PER_BATCH} ảnh.`,
"error"
);
return arr.slice(0, MAX_FILES_PER_BATCH);
}
return arr;
}
function createCard({ url }) {
const card = document.createElement("div");
card.className = "card";
const thumb = document.createElement("div");
thumb.className = "thumb";
thumb.innerHTML = `<img alt="Cloudinary-Upload" src="${url}">`;
const linkRow = document.createElement("div");
linkRow.className = "link-row";
const input = document.createElement("input");
input.className = "image-url";
input.readOnly = true;
input.value = url;
input.addEventListener("focus", () => input.select());
const btn = document.createElement("button");
btn.className = "copy-btn";
btn.type = "button";
btn.textContent = "Copy";
btn.addEventListener("click", () => copyText(url));
linkRow.appendChild(input);
linkRow.appendChild(btn);
card.appendChild(thumb);
card.appendChild(linkRow);
return card;
}
(function () {
const dropzone = document.querySelector(".dropzone");
const fileInput = dropzone.querySelector("input.input");
const results = document.querySelector(".results");
const actions = document.querySelector(".actions");
const copyAllBtn = document.querySelector(".copy-all-btn");
const clearListBtn = document.querySelector(".clear-list-btn");
let uploadingCount = 0;
createLoading();
function setActionsVisibleIfHasResults() {
const hasResults = !!results.querySelector(".card");
actions.classList.toggle("hidden", !hasResults);
}
function setActionsEnabled(enabled) {
copyAllBtn.disabled = !enabled;
clearListBtn.disabled = !enabled;
}
["dragenter", "dragover"].forEach(ev => {
dropzone.addEventListener(ev, e => {
e.preventDefault();
e.stopPropagation();
dropzone.classList.add("dropzone-dragging");
});
});
["dragleave", "drop"].forEach(ev => {
dropzone.addEventListener(ev, e => {
e.preventDefault();
e.stopPropagation();
dropzone.classList.remove("dropzone-dragging");
});
});
dropzone.addEventListener("drop", e => {
const dt = e.dataTransfer;
const files = clampFiles((dt && dt.files) || []);
if (!files.length) return;
multiHandleFiles(files);
});
fileInput.addEventListener("change", (e) => {
const files = clampFiles(e.target.files || []);
if (!files.length) return;
multiHandleFiles(files);
e.target.value = "";
});
async function multiHandleFiles(files) {
const valid = [];
for (const file of files) {
if (!ACCEPT_TYPES.includes(file.type)) {
showToast("Bỏ qua file không hỗ trợ (chỉ JPG/PNG/GIF).", "error");
continue;
}
if (file.size > MAX_SIZE) {
showToast("Bỏ qua file > 5MB.", "error");
continue;
}
valid.push(file);
}
if (!valid.length) return;
uploadingCount += valid.length;
document.body.classList.add("loading");
setActionsVisibleIfHasResults();
setActionsEnabled(false);
try {
const uploads = await Promise.allSettled(valid.map(f => uploadToCloudinary(f)));
let successCount = 0;
uploads.forEach((res, idx) => {
if (res.status === "fulfilled") {
const url = optimizedUrl(res.value.secure_url);
results.prepend(createCard({ url }));
successCount++;
} else {
console.warn("Upload failed:", valid[idx]?.name, res.reason);
showToast(`1 file lỗi: ${valid[idx]?.name || ""}`, "error");
}
});
if (successCount) {
showToast(`Đã upload ${successCount} ảnh`);
setActionsVisibleIfHasResults();
}
} finally {
uploadingCount -= valid.length;
if (uploadingCount <= 0) {
document.body.classList.remove("loading");
const hasResults = !!results.querySelector(".card");
setActionsEnabled(hasResults);
}
}
}
copyAllBtn.addEventListener("click", () => {
const inputs = results.querySelectorAll(".image-url");
if (!inputs.length) return showToast("Chưa có link để copy.", "error");
const all = Array.from(inputs).map(i => i.value).join("\n");
copyText(all);
});
clearListBtn.addEventListener("click", () => {
results.innerHTML = "";
setActionsVisibleIfHasResults();
setActionsEnabled(false);
showToast("Đã xoá danh sách.", "warn");
});
})();
</script>
Cách sử dụng
- Mở trang upload ảnh vừa tạo trên Blogspot.
- Kéo/thả nhiều ảnh hoặc nhấn để chọn từ máy tính (JPG, PNG, GIF, dưới 5MB, tối đa 20 ảnh/lần).
- Đợi vài giây để ảnh upload lên Cloudinary.
- Nhận danh sách link ảnh (bắt đầu bằng
https://res.cloudinary.com/
) kèm hình xem trước. - Nhấn Copy để sao chép từng link hoặc Copy tất cả link để lấy toàn bộ.
Đặc điểm nổi bật
- Upload nhanh gọn: Kéo thả hoặc chọn file từ máy, vài giây có ngay link.
- Copy link 1 chạm: Mỗi ảnh có nút Copy riêng, hoặc Copy tất cả link một lần.
- Xem trước trực tiếp: Ảnh hiển thị preview ngay sau khi upload.
- Tự động tối ưu: Tích hợp sẵn
f_auto,q_auto
giúp ảnh nhẹ mà vẫn nét. - Giới hạn thông minh: Chặn file > 5MB, chỉ cho phép JPG/PNG/GIF, tối đa 20 ảnh/lần.
- Miễn phí & ổn định: Dựa trên Cloudinary CDN, gói free thoải mái cho nhu cầu blog cá nhân.
- Tùy biến dễ dàng: Các màu sắc, giới hạn upload, thông báo... đều đã đánh dấu để bạn đổi theo gu.
Video hướng dẫn chi tiết
Nếu bạn không muốn đọc thì có thể xem video hướng dẫn cách tạo trang up ảnh lấy link miến phí nhé!
Lời kết
Vậy là anh em đã có trong tay một tool up ảnh lấy link miễn phí, tiện lợi và mượt mà trên Blogger. Chỉ cần vài thao tác kéo thả là có ngay link chuẩn HTTPS, tối ưu sẵn f_auto,q_auto
, copy một phát dán được liền.
Mình đã cố gắng viết chi tiết và để sẵn chỗ để các bạn dễ tuỳ biến theo gu. Tuy nhiên chắc chắn còn nhiều chỗ có thể cải tiến thêm.
Nếu có góp ý, ý tưởng mới, hay muốn mình phát triển thêm tính năng nào (ví dụ upload nhiều ảnh nâng cao, rename file, hoặc quản lý folder), cứ mạnh dạn comment bên dưới. Mình đọc hết và sẽ cố gắng cập nhật trong các phiên bản tiếp theo.