Lưới tải ảnh
Dùng khi cần tải nhiều ảnh và bỏ ra từng tấm — ảnh công trường, hoá đơn, giấy tờ.
Xem trước trực tiếp
Nguồn
<div class="dc-ph">
<div class="dc-ph__head">
<h3 class="dc-ph__title">현장 사진</h3>
<span class="dc-ph__count">3 / 10</span>
</div>
<ul class="dc-ph__grid">
<li class="dc-ph__cell">
<span class="dc-ph__thumb" role="img" aria-label="현장 사진 1"><svg class="dc-ph__ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M3 17l5-6 4 4 3-3 6 5" /><circle cx="8" cy="7" r="2" /></svg></span>
<span class="dc-ph__badge">대표</span>
<button class="dc-ph__x" type="button"><span class="dc-ph__xdot" aria-hidden="true">✕</span><span class="dc-sr">현장 사진 1 지우기</span></button>
</li>
<li class="dc-ph__cell">
<span class="dc-ph__thumb" role="img" aria-label="현장 사진 2"><svg class="dc-ph__ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M3 17l5-6 4 4 3-3 6 5" /><circle cx="8" cy="7" r="2" /></svg></span>
<button class="dc-ph__x" type="button"><span class="dc-ph__xdot" aria-hidden="true">✕</span><span class="dc-sr">현장 사진 2 지우기</span></button>
</li>
<li class="dc-ph__cell">
<span class="dc-ph__thumb" role="img" aria-label="현장 사진 3"><svg class="dc-ph__ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M3 17l5-6 4 4 3-3 6 5" /><circle cx="8" cy="7" r="2" /></svg></span>
<button class="dc-ph__x" type="button"><span class="dc-ph__xdot" aria-hidden="true">✕</span><span class="dc-sr">현장 사진 3 지우기</span></button>
</li>
<li class="dc-ph__cell dc-ph__cell--add">
<input class="dc-ph__file" id="dc-ph-f1" type="file" accept="image/*" multiple />
<label class="dc-ph__add" for="dc-ph-f1">
<svg class="dc-ph__plus" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 5v14M5 12h14" /></svg>
<span class="dc-ph__addtext">사진 넣기</span>
</label>
</li>
</ul>
<p class="dc-ph__hint">얼굴과 차량 번호판은 가려 주세요. 한 장에 10MB까지.</p>
</div>
.dc-ph { padding:var(--dc-4); background:var(--dc-bg); font-family:var(--dc-font); }
.dc-ph__head { display:flex; align-items:baseline; justify-content:space-between; gap:var(--dc-2); margin-bottom:var(--dc-3); }
.dc-ph__title { margin:0; font-size:15px; font-weight:600; color:var(--dc-text); }
.dc-ph__count { font-family:var(--dc-mono); font-size:13px; color:var(--dc-text-mute); }
.dc-ph__grid { display:grid; grid-template-columns:repeat(3, 1fr); gap:var(--dc-2); margin:0; padding:0; list-style:none; }
.dc-ph__cell { position:relative; aspect-ratio:1 / 1; border-radius:var(--dc-r-md); background:var(--dc-sunken); overflow:hidden; }
.dc-ph__thumb { display:flex; align-items:center; justify-content:center; width:100%; height:100%; background:linear-gradient(135deg, var(--dc-sunken), color-mix(in srgb, var(--dc-brand) 16%, var(--dc-sunken))); color:var(--dc-text-mute); }
.dc-ph__ico { width:30px; height:30px; fill:none; stroke:currentColor; stroke-width:1.6; stroke-linecap:round; stroke-linejoin:round; }
.dc-ph__thumb--fail { background:var(--dc-danger-soft); color:var(--dc-danger); }
.dc-ph__badge { position:absolute; left:var(--dc-1); bottom:var(--dc-1); padding:2px var(--dc-2); border-radius:var(--dc-r-xs); background:var(--dc-brand); color:var(--dc-text-on); font-size:11px; font-weight:700; }
.dc-ph__x { position:absolute; top:0; right:0; display:flex; align-items:flex-start; justify-content:flex-end; width:44px; height:44px; padding:var(--dc-1); border:0; background:transparent; }
.dc-ph__xdot { display:flex; align-items:center; justify-content:center; width:26px; height:26px; border-radius:var(--dc-r-full); background:color-mix(in srgb, var(--dc-text) 72%, transparent); color:var(--dc-bg); font-size:13px; line-height:1; }
.dc-ph__cell--add { background:transparent; }
.dc-ph__file { position:absolute; width:1px; height:1px; opacity:0; }
.dc-ph__add { display:flex; flex-direction:column; align-items:center; justify-content:center; gap:var(--dc-1); width:100%; height:100%; border:1px dashed var(--dc-line-field); border-radius:var(--dc-r-md); background:var(--dc-surface); color:var(--dc-text-sub); cursor:pointer; }
.dc-ph__add:hover { background:var(--dc-sunken); }
.dc-ph__file:focus-visible + .dc-ph__add { outline:2px solid var(--dc-brand); outline-offset:2px; }
.dc-ph__plus { width:24px; height:24px; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; }
.dc-ph__addtext { font-size:12px; font-weight:600; }
.dc-ph__bar { position:absolute; left:var(--dc-2); right:var(--dc-2); bottom:var(--dc-2); height:6px; border-radius:var(--dc-r-full); background:color-mix(in srgb, var(--dc-text) 24%, transparent); overflow:hidden; }
.dc-ph__barfill { display:block; width:62%; height:100%; border-radius:var(--dc-r-full); background:var(--dc-brand); }
.dc-ph__retry { position:absolute; left:50%; top:50%; transform:translate(-50%, -50%); min-width:60px; min-height:44px; padding:0 var(--dc-2); border:1px solid var(--dc-danger); border-radius:var(--dc-r-sm); background:var(--dc-surface); color:var(--dc-danger); font-family:var(--dc-font); font-size:13px; font-weight:600; }
.dc-ph__empty { position:relative; }
.dc-ph__big { display:flex; flex-direction:column; align-items:center; justify-content:center; gap:var(--dc-2); min-height:150px; padding:var(--dc-5); border:1px dashed var(--dc-line-field); border-radius:var(--dc-r-md); background:var(--dc-surface); color:var(--dc-text-sub); text-align:center; cursor:pointer; }
.dc-ph__big:hover { background:var(--dc-sunken); }
.dc-ph__file:focus-visible + .dc-ph__big { outline:2px solid var(--dc-brand); outline-offset:2px; }
.dc-ph__emptynote { font-size:12px; line-height:1.6; color:var(--dc-text-mute); }
.dc-ph__hint { margin:var(--dc-3) 0 0; font-size:12px; line-height:1.6; color:var(--dc-text-mute); }
Cách dùng
- Ghi rõ được tải bao nhiêu ảnh, bằng con số đặt trên lưới
- Để ô thêm ảnh luôn ở cuối, để nó nằm cùng một chỗ bất kể đã tải mấy tấm
- Ảnh đang tải hay tải lỗi thì hiện trong chính ô của nó, kèm cách thử lại
Điểm cần lưu ý
- Nút xoá nên có vùng bấm 44px nhưng vòng tròn nhìn thấy thì nhỏ, để không che mất ảnh
- Nói rõ xoá tấm nào — 'Xoá ảnh công trường 1', chứ không phải một dấu ✕ trơ trọi