Đến nội dung chính

Dải ảnh nhỏ

Một hàng để chọn một ảnh trong nhiều ảnh, hoặc để xem lại những ảnh vừa đính kèm.

Phương tiện

Xem trước trực tiếp

Nguồn

<fieldset class="dc-ts">
  <legend class="dc-ts__legend">대표 사진 고르기</legend>
  <div class="dc-ts__strip">
    <label class="dc-ts__item">
      <input class="dc-ts__radio" type="radio" name="dc-ts-cover" checked>
      <span class="dc-ts__thumb" aria-hidden="true"></span>
      <span class="dc-ts__name">터파기</span>
    </label>
    <label class="dc-ts__item">
      <input class="dc-ts__radio" type="radio" name="dc-ts-cover">
      <span class="dc-ts__thumb" aria-hidden="true"></span>
      <span class="dc-ts__name">철근</span>
    </label>
    <label class="dc-ts__item">
      <input class="dc-ts__radio" type="radio" name="dc-ts-cover">
      <span class="dc-ts__thumb" aria-hidden="true"></span>
      <span class="dc-ts__name">거푸집</span>
    </label>
    <label class="dc-ts__item">
      <input class="dc-ts__radio" type="radio" name="dc-ts-cover">
      <span class="dc-ts__thumb" aria-hidden="true"></span>
      <span class="dc-ts__name">타설</span>
    </label>
    <label class="dc-ts__item">
      <input class="dc-ts__radio" type="radio" name="dc-ts-cover">
      <span class="dc-ts__thumb" aria-hidden="true"></span>
      <span class="dc-ts__name">양생</span>
    </label>
    <label class="dc-ts__item">
      <input class="dc-ts__radio" type="radio" name="dc-ts-cover">
      <span class="dc-ts__thumb" aria-hidden="true"></span>
      <span class="dc-ts__name">마감</span>
    </label>
  </div>
</fieldset>
.dc-ts {
  /* 사진 위에 올라가는 글자와 배지는 두 테마에서 같은 쪽이어야 한다. 스크림이
     밝은 모드에서도 어두우므로 글자는 늘 밝은 쪽, 배지 바탕은 늘 어두운 쪽이다.
     고정색을 적는 대신 토큰을 양쪽에서 뒤집어 그 두 값을 만든다. */
  --dc-on-media: var(--dc-text-on);
  --dc-media-ink: var(--dc-text);
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
  font-family: var(--dc-font);
}
@media (prefers-color-scheme: dark) {
  .dc-ts {
    --dc-on-media: var(--dc-text);
    --dc-media-ink: var(--dc-text-on);
  }
}
.dc-ts__legend {
  padding: 0 var(--dc-4) var(--dc-2);
  font-size: 13px;
  font-weight: 700;
  color: var(--dc-text-sub);
}
.dc-ts__strip {
  display: flex;
  gap: var(--dc-2);
  padding: var(--dc-2) var(--dc-4) var(--dc-3);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.dc-ts__strip::-webkit-scrollbar { display: none; }
.dc-ts__item {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  gap: var(--dc-1);
  width: 68px;
  scroll-snap-align: start;
  cursor: pointer;
}
.dc-ts__radio {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  appearance: none;
}
.dc-ts__thumb {
  display: block;
  width: 68px;
  height: 68px;
  border-radius: var(--dc-r-sm);
  background: linear-gradient(135deg,
    color-mix(in srgb, var(--dc-brand) 30%, var(--dc-sunken)),
    var(--dc-sunken));
  outline: 2px solid transparent;
  outline-offset: 2px;
  transition: outline-color var(--dc-fast), opacity var(--dc-fast);
}
.dc-ts__item:nth-child(3n + 2) .dc-ts__thumb,
.dc-ts__cell:nth-child(3n + 2) .dc-ts__thumb {
  background: linear-gradient(135deg,
    color-mix(in srgb, var(--dc-info) 30%, var(--dc-sunken)),
    var(--dc-sunken));
}
.dc-ts__item:nth-child(3n) .dc-ts__thumb,
.dc-ts__cell:nth-child(3n) .dc-ts__thumb {
  background: linear-gradient(135deg,
    color-mix(in srgb, var(--dc-ok) 28%, var(--dc-sunken)),
    var(--dc-sunken));
}
.dc-ts__radio:checked + .dc-ts__thumb { outline-color: var(--dc-brand); }
.dc-ts__radio:focus-visible + .dc-ts__thumb {
  outline-color: var(--dc-brand);
  box-shadow: 0 0 0 4px var(--dc-brand-soft);
}
.dc-ts__name {
  font-size: 11px;
  line-height: 1.3;
  text-align: center;
  color: var(--dc-text-sub);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.dc-ts__radio:checked ~ .dc-ts__name {
  color: var(--dc-brand-text);
  font-weight: 700;
}
.dc-ts__cell {
  position: relative;
  flex: 0 0 auto;
  scroll-snap-align: start;
}
.dc-ts__del {
  position: absolute;
  top: calc(-1 * var(--dc-1));
  right: calc(-1 * var(--dc-1));
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 2px solid var(--dc-surface);
  border-radius: var(--dc-r-full);
  background: color-mix(in srgb, var(--dc-media-ink) 72%, transparent);
  color: var(--dc-on-media);
  font-size: 12px;
  line-height: 1;
}
.dc-ts__del::after {
  content: "";
  position: absolute;
  inset: -9px;
}
.dc-ts__add {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 68px;
  height: 68px;
  border: 1px dashed var(--dc-line-field);
  border-radius: var(--dc-r-sm);
  background: var(--dc-sunken);
  color: var(--dc-text-sub);
  font-size: 22px;
  line-height: 1;
}
.dc-ts__add:active { background: var(--dc-surface); }
.dc-ts--wide .dc-ts__thumb { width: 112px; height: 63px; }
.dc-ts__dur {
  position: absolute;
  right: var(--dc-1);
  bottom: var(--dc-1);
  padding: 0 var(--dc-1);
  border-radius: var(--dc-r-xs);
  background: color-mix(in srgb, var(--dc-media-ink) 60%, transparent);
  color: var(--dc-on-media);
  font-size: 11px;
  font-weight: 700;
}

Cách dùng

  • Nếu dùng để chọn, hãy làm bằng radio hoặc checkbox, và viền rõ cái đang chọn
  • Đừng để hàng kết thúc sát mép màn hình — chừa một khoảng nhỏ để thấy còn nữa
  • Nhãn chỉ một dòng; cần hai dòng nghĩa là màn hình này nên dùng danh sách chứ không phải ảnh nhỏ
  • Với video, in thời lượng ở góc dưới phải để người ta biết trước khi bấm

Điểm cần lưu ý

  • Nút ✕ 26px cần thêm ::after vô hình để vùng bấm đạt 44px
  • display:none cho radio sẽ mất cách chọn bằng bàn phím — giữ nó trong suốt và chuyển viền tiêu điểm sang ảnh nhỏ
  • Ảnh mang ý nghĩa thì đặt aria-label; đã có nhãn chữ bên ngoài thì ẩn bằng aria-hidden

Tiếp theo