GODRICH

날씨 위젯 9종 — 복붙하면 되는 온도·시계·아이콘 코드

날씨 위젯은 사이트에 붙이는 순간 가장 먼저 보이는 작은 화면 묶음입니다. 「날씨 위젯」 을 검색하는 사람이 원하는 건 큰 그림이 아니라 온도 카드·시간별 띠·해의 길 같은 부품 하나인데, 여기 모은 아홉 벌은 복붙하면 되는 HTML·SCSS·JS 로 짜여 그 자리에서 움직입니다.

자동 재생 · 눌러서 해당 항목으로 · 전부 한 zip

아홉 개는 기술 난이도 순이 아니라 하루가 지나가는 순서로 놓았습니다. 지금 하늘을 보여 주는 카드(01)에서 시작해, 상태를 그리는 움직이는 아이콘(02)을 지나, 남은 시간이 띠로 이어지고(03), 주간 범위가 막대로 자리를 잡으며(04), 해가 아치를 지나가고(05), 지수 바늘이 등급을 짚고(06), 비와 바람이 자라고 도는가 하면(07), 단위를 고르면 숫자가 통째로 환산되고(08), 마지막에는 실제 시계가 하늘 색을 이어받습니다(09). 아홉 벌이 같은 색·간격·이징 값을 나눠 쓰기 때문에 몇 개를 한 페이지에 섞어도 톤이 어긋나지 않습니다. 임베드 위젯 모음의 다른 축과 함께 두면 좋습니다 — 위젯 패턴 모음 이나 활동 피드 위젯 을 보면 같은 규격으로 붙는 걸 확인할 수 있습니다.

01맑음에서 비로 바꾸면 하늘색도 문구도 함께 물드는 카드

맑음·구름·비·눈 네 조건 중 하나를 고르면 카드의 하늘색이 steps(1, end) 컷으로 맞바뀌고, 온도·상태 문구·최저·최고가 한 세트로 따라옵니다. 컷만 있으면 움직인 프레임이 모자라니 아이콘이 한 번 솟았다 내려앉는 팝을 곁들였고, 누적면적 33.208% · 강도 58.2 로 측정됐습니다. 대시보드 상단 날씨 요약이나 여행·야외 행사 페이지에 씁니다.

opacitytransitionaria-pressed
// 하늘 두 겹 — 상태 클래스가 한 겹만 보이게 한다. 교체는 컷(크로스페이드 금지)
.wc.is-cloudy .wc__sky--a,
.wc.is-rainy .wc__sky--a,
.wc.is-snowy .wc__sky--a { opacity: 0; }
.wc.is-rainy .wc__sky--b { opacity: 1; }

// 실제 클릭: 아이콘이 한 번 솟았다 내려앉는다
@keyframes wc-pop {
  0% { transform: translateY(3px) scale(.88); }
  55% { transform: translateY(-3px) scale(1.04); }
  100% { transform: translateY(0) scale(1); }
}

02멈춰 있지 않은 날씨 아이콘 — 해·구름·비·눈

해는 광선이 돌고 구름은 흐르며 빗방울은 서로 엇갈리게 떨어지고 번개는 쉬었다 번쩍입니다. 주기는 전부 2초의 약수(2s·1s)라 캡처 창 안에 한 바퀴가 온전히 담기고, 딜레이가 걸린 빗방울·눈송이에는 animation-fill-mode: backwards 까지 필요합니다. 누적면적 2.443% · 움직인 프레임 23/23, 예보 목록의 조건 아이콘에 씁니다.

animation-delaystepsinfinite
// 빗방울 세 개: 1초 주기, 딜레이로 엇갈린다 — 끝에서 옅어져 사라지는 것까지 포함
@keyframes wi-dropfall {
  0% { transform: translateY(0); opacity: 0; }
  18% { opacity: 1; }
  72% { opacity: 1; }
  100% { transform: translateY(12px); opacity: 0; }
}
.wi__drop {
  position: absolute;
  animation-name: wi-dropfall;
  animation-duration: 1s;
  animation-iteration-count: infinite;
  animation-fill-mode: backwards; // 지연 구간에도 0% 상태가 유지된다
}
.wi__drop--2 { left: 27px; animation-delay: .33s; }

03지금 시각 칸에 불이 들어오는 시간별 띠

지금부터 밤까지의 시간 칸이 가로로 이어져 있고, new Date().getHours() 로 찾은 현재 시각 칸에 점이 켜지며 살짝 앞으로 나옵니다. 스크롤은 scroll-snap-type: x mandatory 덕분에 칸 단위로 딱 맞춰 멈춥니다. 누적면적 10.719% · 강도 50.8, 시간별 예보나 등하교 시간대 안내에 씁니다.

scroll-snap-typearia-currentscroll-snap-align
// 실제 시각과 가장 가까운 칸을 찾아 점을 켜고 aria-current 를 붙인다.
var hsNow = new Date().getHours();
var hsBest = 0;
var hsGap = 99;
for (var i = 0; i < hsCells.length; i++) {
  var gap = Math.abs(Number(hsCells[i].getAttribute('data-hour')) - hsNow);
  if (gap < hsGap) { hsGap = gap; hsBest = i; }
}
hsCells[hsBest].classList.add('is-now');
hsCells[hsBest].setAttribute('aria-current', 'true');

04일주일 범위 안에서 제자리를 찾는 5일 예보 막대

다섯 날짜의 최저~최고 온도 막대가 주간 전체 범위 안에서 제자리에 놓입니다. 자리 계산은 JS 가 data-lo·data-hi 를 주간 최소·최대로 정규화해 left·width % 를 정적으로 넣는 방식이고, 등장은 width 재계산과 scaleX 눌림을 피하려고 clip-path 위프로 처리해 다섯 줄이 0.12초 간격으로 차례차례 펴집니다. 누적면적 2.413% · 강도 80.5 · 움직인 프레임 18/23, 주간 예보 리스트나 의상·짐 준비 안내에 씁니다.

leftwidthtabular-nums
// 막대의 left/width 는 주간 전체 범위 안에서 비율로 계산한다
var drSpan = drMax - drMin;
for (var j = 0; j < drDays.length; j++) {
  var d = drDays[j];
  var bar = d.row.querySelector('.dr__bar');
  bar.style.left = ((d.lo - drMin) / drSpan * 100) + '%';
  bar.style.width = ((d.hi - d.lo) / drSpan * 100) + '%';
}

05하루의 해가 아치를 따라 올랐다 지는 해의 길

타원 아치 위의 해가 슬라이더로 고른 시각의 높이에 붙어 굴러가며 아침·낮·저녁 하늘색이 그 아래에서 바뀝니다. 삼각함수는 없습니다 — 해 요소에 offset-path: path("M 20 71 A 120 60 0 0 1 220 71") 를 실어 두고 offset-distance 만 바꾸면 브라우저가 경로 위를 움직여 줍니다. 누적면적 16.797% · 강도 79.1 로 23/23 프레임 내내 쉬지 않고 움직이며, 일출일몰 카드나 야외 촬영 시간 안내에 씁니다.

offset-rotatestrokearia-valuenow
// 아치를 따라가는 길 — SVG 아치(M 20 80 A 120 60 0 0 1 220 80)와 반지름·시작끝 x 는 같고
// y 만 9px(해 아이콘 18px 의 절반) 올려 해 심이 선 위에 놓이게 했다
.sa__sun {
  offset-path: path("M 20 71 A 120 60 0 0 1 220 71");
  offset-distance: 57%;
  offset-rotate: 0deg;
}

06바늘이 등급 띠 위를 짚어 가리키는 자외선 게이지

반원에 낮음부터 위험까지 다섯 등급의 색 띠가 이어져 있고, 값을 바꾸면 바늘이 transform: rotate 로 각도를 잡아 해당 띠에 멈추며 등급 이름과 지침 문장이 바뀝니다. 눈금은 conic-gradient 를 36도씩 끊어 만들었고, 원반과 읽는 칸은 2열 grid 로, 바늘의 회전축은 허브 중심으로 잡았습니다. 누적면적 2.125% · 강도 74.1 · 움직인 프레임 20/23, 자외선·대기 지수 카드나 야외 활동 안내에 씁니다.

conic-gradientgridtransform-origin
// 정사각 원반을 50% 반경으로 그리고 위쪽 반원만 clip-path 로 보여준다
.uv__disc {
  width: 120px;
  height: 120px;
  border-radius: 50%;
  background: conic-gradient(from 270deg at 50% 50%,
    #7ed957 0 36deg, #ffe45c 36deg 72deg, #ffb020 72deg 108deg,
    #f2691f 108deg 144deg, #d7263d 144deg 180deg,
    rgba(255, 247, 230, 0) 180deg 360deg);
  clip-path: inset(0 0 60px 0);
}

07강수 확률이 자라는 막대와 풍향을 짚는 나침반

시간마다 강수 확률 막대가 아래에서부터 자라 오르고, 옆 나침반의 바늘은 풍향으로 돌아가 속도 숫자와 함께 방향 이름을 바꿉니다. 막대 높이값은 --h 커스텀 속성에 정적으로 들어 있고 성장은 scaleY 로만 처리했습니다. 강도는 105.1 로 아홉 중에서도 손꼽히게 높고, 세탁·야외 일정 안내에 씁니다.

scaleYrotatetransform-origin
// 막대 성장은 scaleY 뿐 — 높이값(--h)은 정적으로 들어 있다
.pw__bar {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: var(--h);
  transform-origin: bottom; // 데모 루프의 scaleY 가 아래에서 자란다
  background: linear-gradient(180deg, #4a9fe8, $subject-blue);
}

08한 번 누르면 카드 안 모든 온도가 환산되는 단위 토글

섭씨·화씨를 고르면 큰 온도·시간별 칸·주간 숫자가 같은 순간에 전부 환산되고, 고른 단위는 localStorage 에 저장됐다가 다음에 열면 그대로입니다. 저장은 try/catch 로 감쌌고 숫자는 font-variant-numeric: tabular-nums 로 흔들리지 않습니다. 누적면적 1.576% · 강도 114.6, 글로벌 서비스의 단위 설정에 씁니다.

localStoragearia-pressedMath.round
// 단위를 고르면 카드 안 모든 온도가 같은 순간에 환산된다
function utShow(unit) {
  for (var i = 0; i < utCells.length; i++) {
    var c = Number(utCells[i].getAttribute('data-c'));
    utCells[i].textContent = unit === 'f' ? String(Math.round(c * 9 / 5 + 32)) : String(c);
  }
}
function utRemember(unit) {
  try { localStorage.setItem('ut-unit', unit); } catch (err) { /* 저장이 막힌 창에서는 이번 화면만 */ }
}

09실제 시간이 지나면 하늘도 인사말도 바뀌는 시계 위젯

분이 바뀌는 순간마다 시계가 조용히 앞으로 가고 그 시간대의 하늘색과 인사말이 이어집니다. setInterval 은 매초 돌지만 글자를 고치는 건 분이 바뀔 때뿐이고, 시각 요소는 role="timer" aria-live="off" 로 묵음해 뒀습니다. 누적면적 11.155% · 움직인 프레임 16/23, 키오스크 대기 화면에 씁니다.

setIntervalaria-livetabular-nums
// 매초 돌되 분이 바뀔 때만 글자를 고친다
function lcTick() {
  var d = new Date();
  if (d.getMinutes() !== lcLastMinute) {
    lcLastMinute = d.getMinutes();
    lcNow.textContent = lcPad(d.getHours()) + ':' + lcPad(d.getMinutes());
    lcDate.textContent = lcWeek[d.getDay()] + ' · ' + (d.getMonth() + 1) + '/' + d.getDate();
    lcRoot.classList.add(lcTheme(d.getHours()));
  }
}
setInterval(lcTick, 1000);

어디서 깨지는가 — 함정

첫째, 반원 게이지의 수평선 아래 슬리브. 06 번 게이지를 처음엔 짧은 상자에 conic-gradient 피벗을 50% 100% 로 두고 border-radius: 50% 50% 0 0 / 100% 100% 0 0 로 반원을 만들었는데, 수평선 바로 아래에 등급색 슬리브가 가늘게 남았습니다. 반경 슬래시 표기도 간격 규칙에 걸렸습니다. 정사각 원반을 border-radius: 50% 로 그린 뒤 clip-path: inset(0 0 60px 0) 로 중심선까지 자르는 구조로 바꿔 해결했습니다.

둘째, 단위 접미사의 유령. 08 번의 °C/°F 를 처음엔 ::after 로 겹쳐 바꿨다가 대표 프레임에 75°C 라는 글자가 찍혔습니다 — 숫자는 컷으로 바뀌었는데 접미사가 두 겹으로 겹친 것입니다. 접미사도 숫자와 같은 v/alt 두 겹 steps(1, end) 컷 교체로 바꿔 유령을 없앴습니다. 이렇게 잠긴 zip 의 비밀번호는 639h7wyh 이고, 안에는 이 아홉 벌의 바닐라판과 React 판이 통째로 들어 있습니다.

셋째, 스냅이 트랙을 붙잡는 문제. 03 번의 scroll-snap-type: mandatory 는 미리보기 루프가 칸 줄을 transform 으로 밀 때 그걸 다시 스냅해 움직임을 먹어버립니다. .is-demo 동안에만 scroll-snap-type: none 을 주고, 사람이 손대는 순간 루프를 멈춰 실제 스크롤로 쓰게 뒀습니다. 스크롤 상자와 칸 줄을 한 요소에 두면 translateX(-100%) 의 기준이 상자 자기 폭이 되어 카드 한 장만 밀려나가는 사고도 납니다 — 둘은 반드시 떼어 놓아야 합니다.

넷째, 브라우저 지원. 이번에 쓴 기능의 지원 범위를 caniuse 로 실측한 값이 offset-path Chrome 46+ · Safari 16+ · Firefox 72+ · Edge 79+, 전역 96.35% — scroll-snap Chrome 69+ · Safari 11+ · Firefox 68+ · Edge 79+, 전역 96.8% — conic-gradient Chrome 69+ · Safari 12.1+ · Firefox 83+ · Edge 79+, 전역 96.44% (2026-09-12 기준). 사파리 15 이하가 걸리는 건 offset-path 뿐이니, 해의 길을 뺀 여덟 벌은 그대로 쓰고 05 번만 조건부로 붙이면 됩니다.

접근성

prefers-reduced-motion 블록의 선언에는 !important 를 붙입니다. 루프용 클래스가 특이도에서 이기면 몸을 움직이기 힘든 사람의 화면에서도 위젯이 계속 돕니다. 컷 교체로 겹쳐 둔 두 번째 값은 reduce 에서 감추고 실제 값만 남깁니다. 시계(09)는 role="timer" aria-live="off" — 계속 바뀌는 값을 낭독하면 안 되기 때문입니다. 단위·조건 버튼은 aria-pressed, 지금 시각 칸은 aria-current="true", 바람 라벨처럼 값이 바뀌는 짧은 알림은 aria-live="polite" 로 읽히게 했고, 모든 조작 요소에 :focus-visible 테두리가 켜집니다. 대시보드 위젯 규격이 궁금하면 대시보드 위젯 모음 도 같은 규칙으로 짜여 있습니다.

FAQ

날씨 API 없이도 되나요?

됩니다. 아홉 벌 전부 화면 안 데이터로 돌아갑니다. 값의 주인을 API 가 아니라 화면 글자와 Date 로 둔 것도 그 때문입니다 — 나중에 진짜 API 를 붙이고 싶으면 숫자를 갈아 끼우기만 하면 됩니다.

offset-path 는 사파리에서 되나요?

Safari 16+ 에서 됩니다(전역 96.35%, caniuse 2026-09-12). 그 아래 버전을 섞어 써야 한다면 05 번 해의 길만 빼고 여덟 벌을 먼저 붙이고, 조건부로 05 번을 얹는 방식을 권합니다.

섭씨·화씨 선택은 어떻게 기억하나요?

localStorage 에 단위를 한 글자 저장하고 열 때 되살립니다. 저장이 막힌 창에서는 try/catch 로 넘어가 그 화면에서만 유효합니다 — 위젯이 멈추는 일은 없습니다.

변형 3개

바꾸는 값 원래 값 바꾼 값 생기는 것
$duration 2s 1s 해의 여정이 반으로 줄고 구름 흐름·광선 회전도 같이 빨라진다 — 두 배로 쾌활해진다
$ease-spring spring 곡선 linear 나침반 바늘(07)과 지수 바늘(06)이 튀지 않고 등속으로 흐른다 — 기상청 느낌
$color (하늘) 파랑 계열 주황 계열 같은 구조로 저녁 위젯이 된다 — 09 번과 짝을 지어 아침·저녁 세트로 쓸 수 있다
압축 비밀번호를 입력하세요

비밀번호는 이 글 본문 안에 있습니다. 읽다 보면 나옵니다.