QR Code Generator with HTML, CSS & JavaScript
20 DAYS 20 PROJECT CHALLENGE
Day #12
Project Overview
A small QR Code Generator built with HTML, CSS and JavaScript that turns user text (URL, plain text, contact info, etc.) into a downloadable QR code image. This demo uses a third-party QR service (QRServer API) via fetch() to request generated QR images, and demonstrates building query parameters, handling blobs, showing previews, and offering a download. Great for learning how to integrate a simple external API and handle binary responses in the browser.
Key Features
- Enter text (or pastes) and generate a QR code.
- Configure size, margin, and error correction level (L / M / Q / H).
- Shows a live preview.
- Downloads generated QR as PNG.
- Option to open the QR in a new tab.
- Uses
fetch()to retrieve the image as aBloband displays it withURL.createObjectURL(). - Graceful UI states: loading, invalid input, and error handling.
HTML Code
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Day 12 — QR Code Generator</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<main class="card" role="main" aria-labelledby="title">
<header>
<div class="logo">QR</div>
<div>
<h1 id="title">Day 12: QR Code Generator</h1>
<p class="lead">Generate and download QR codes from any text using the QRServer API and <code>fetch()</code>.</p>
</div>
</header>
<section class="panel">
<label class="field">
<span class="label">Text or URL</span>
<textarea id="inputText" placeholder="Enter a URL or text to convert to QR code" rows="3"></textarea>
</label>
<div class="row">
<label class="field small">
<span class="label">Size (px)</span>
<input id="size" type="number" min="100" max="2000" value="300" />
</label>
<label class="field small">
<span class="label">Margin (0-10)</span>
<input id="margin" type="number" min="0" max="10" value="2" />
</label>
<label class="field small">
<span class="label">Error correction</span>
<select id="ecc">
<option value="L">L (7%)</option>
<option value="M" selected>M (15%)</option>
<option value="Q">Q (25%)</option>
<option value="H">H (30%)</option>
</select>
</label>
</div>
<div class="actions">
<button id="generateBtn" class="btn">Generate QR</button>
<button id="openBtn" class="btn secondary" disabled>Open in new tab</button>
<button id="downloadBtn" class="btn secondary" disabled>Download PNG</button>
</div>
<div id="status" class="muted" aria-live="polite">Enter text and click <strong>Generate QR</strong>.</div>
<div class="preview-wrap">
<div id="preview" class="preview" aria-hidden="true">
<!-- generated QR image will be inserted here -->
</div>
</div>
<details style="margin-top:12px">
<summary>How it works (short)</summary>
<p class="small">The script builds a query URL (e.g. <code>https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=...&ecc=M&margin=2</code>), calls it with <code>fetch()</code> and receives the PNG image as a blob. We create an object URL and display it in an <img> tag and allow the user to download or open the image in a new tab.</p>
</details>
</section>
</main>
<script src="script.js"></script>
</body>
</html>
CSS Code
:root {
--bg: #071026;
--card: #0b1220;
--accent: #7c3aed;
--muted: #9aa4b2;
--white: #e6eef6;
font-family: Inter, system-ui, -apple-system, 'Segoe UI', Roboto, Arial;
}
html,
body {
height: 100%;
}
body {
margin: 0;
background: #002252;
color: var(--white);
display: flex;
align-items: center;
justify-content: center;
padding: 28px;
}
.card {
width: min(820px, 96%);
background: linear-gradient(180deg, rgba(255, 255, 255, 0.02), rgba(255, 255, 255, 0.01));
border-radius: 12px;
padding: 18px;
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.6);
border: 1px solid rgba(255, 255, 255, 0.03);
}
header {
display: flex;
gap: 12px;
align-items: center;
}
.logo {
width: 44px;
height: 44px;
border-radius: 10px;
background: linear-gradient(135deg, var(--accent), #22c1c3);
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
}
h1 {
margin: 0;
font-size: 18px;
}
.lead {
margin: 4px 0 12px;
color: var(--muted);
font-size: 14px;
}
.panel {
margin-top: 12px;
display: flex;
flex-direction: column;
gap: 12px;
}
.field {
display: flex;
flex-direction: column;
}
.field .label {
font-size: 13px;
color: var(--muted);
margin-bottom: 6px;
}
textarea#inputText {
min-height: 64px;
padding: 10px;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.04);
background: transparent;
color: inherit;
outline: none;
resize: vertical;
}
input[type=number],
select {
padding: 8px 10px;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.04);
background: transparent;
color: inherit;
width: 100%;
}
.row {
display: flex;
gap: 12px;
align-items: end;
}
.row .small {
flex: 1;
}
.actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.btn {
padding: 10px 14px;
border-radius: 10px;
border: 0;
background: linear-gradient(90deg, var(--accent), #22c1c3);
color: white;
font-weight: 600;
cursor: pointer
}
.btn.secondary {
background: transparent;
border: 1px solid rgba(255, 255, 255, 0.06);
color: var(--muted)
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed
}
.muted {
color: var(--muted);
font-size: 13px
}
.preview-wrap {
display: flex;
justify-content: center;
margin-top: 12px;
}
.preview {
width: 320px;
height: 320px;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.01), rgba(255, 255, 255, 0.005));
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.03);
}
.preview img {
max-width: 100%;
max-height: 100%;
display: block;
border-radius: 6px;
}
.small {
font-size: 13px;
color: var(--muted)
} Javascript Code
// Day 12 — QR Code Generator using QRServer API and fetch()
// No API key required for QRServer. Example endpoint:
// https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=Hello&ecc=M&margin=2
// DOM
const inputText = document.getElementById('inputText');
const sizeInput = document.getElementById('size');
const marginInput = document.getElementById('margin');
const eccSelect = document.getElementById('ecc');
const generateBtn = document.getElementById('generateBtn');
const downloadBtn = document.getElementById('downloadBtn');
const openBtn = document.getElementById('openBtn');
const statusEl = document.getElementById('status');
const previewEl = document.getElementById('preview');
let currentObjectUrl = null; // track created object URL so we can revoke it
// Utility: build QRServer URL
function buildQrUrl({ data, size = 300, ecc = 'M', margin = 2 }) {
const url = new URL('https://api.qrserver.com/v1/create-qr-code/');
const px = Number(size) || 300;
url.searchParams.set('size', `${px}x${px}`);
url.searchParams.set('data', data || '');
url.searchParams.set('ecc', ecc || 'M');
url.searchParams.set('margin', String(margin || 0));
// Optionally set format param if supported: url.searchParams.set('format','png');
return url.toString();
}
// Show message
function setStatus(msg, isError = false) {
statusEl.textContent = msg;
statusEl.style.color = isError ? '#ffbaba' : '';
}
// Clean preview object URL
function revokeCurrentUrl() {
if (currentObjectUrl) {
URL.revokeObjectURL(currentObjectUrl);
currentObjectUrl = null;
}
previewEl.innerHTML = '';
downloadBtn.disabled = true;
openBtn.disabled = true;
}
// Fetch QR image as blob and show preview
async function generateQr() {
const data = inputText.value.trim();
if (!data) {
setStatus('Please enter text or URL to generate a QR code.', true);
return;
}
const size = Math.max(100, Math.min(2000, Number(sizeInput.value) || 300));
const margin = Math.max(0, Math.min(10, Number(marginInput.value) || 2));
const ecc = eccSelect.value || 'M';
const url = buildQrUrl({ data, size, ecc, margin });
setStatus('Generating QR…');
revokeCurrentUrl();
try {
// Fetch the image as a blob so we can offer download and open in tab
const resp = await fetch(url, { cache: 'no-cache' });
if (!resp.ok) throw new Error(`Network error ${resp.status}`);
const blob = await resp.blob();
// Defensive: ensure it is an image
if (!blob.type.startsWith('image/')) {
throw new Error('Invalid response (not an image).');
}
// Create object URL and show it
currentObjectUrl = URL.createObjectURL(blob);
const img = document.createElement('img');
img.src = currentObjectUrl;
img.alt = 'Generated QR code';
img.onload = () => {
setStatus('QR generated. You can download or open it in a new tab.');
};
img.onerror = () => {
setStatus('Failed to load generated image.', true);
};
previewEl.appendChild(img);
downloadBtn.disabled = false;
openBtn.disabled = false;
} catch (err) {
console.error(err);
setStatus('Failed to generate QR code. Try again.', true);
}
}
// Download current image (as .png)
function downloadCurrentImage() {
if (!currentObjectUrl) return;
const link = document.createElement('a');
link.href = currentObjectUrl;
// create a filename from input (sanitize)
const safe = inputText.value.trim().replace(/[^a-z0-9\-_.]/gi, '_').slice(0, 40) || 'qr';
link.download = `${safe}.png`;
document.body.appendChild(link);
link.click();
link.remove();
}
// Open in new tab
function openInNewTab() {
if (!currentObjectUrl) return;
window.open(currentObjectUrl, '_blank');
}
// Events
generateBtn.addEventListener('click', (e) => {
e.preventDefault();
generateQr();
});
downloadBtn.addEventListener('click', (e) => {
e.preventDefault();
downloadCurrentImage();
});
openBtn.addEventListener('click', (e) => {
e.preventDefault();
openInNewTab();
});
// Convenience: generate on Ctrl+Enter
inputText.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
generateQr();
}
});
// Cleanup on unload
window.addEventListener('beforeunload', () => {
revokeCurrentUrl();
});
Related Projects
Day 10 : Typing Speed Test
Measures typing speed and accuracy.
Concepts: Timers, string comparison, event listeners.
Day 14 : Image Gallery with Modal
Displays images in a grid with a pop-up modal view.
Concepts: DOM traversal, event bubbling.
Day 15 : Light/Dark Mode Toggle
Switch between light and dark themes with one click (and remember the choice).
Concepts: CSS variables, LocalStorage.