07描かれていく経路線
出発ピンから到着ピンまで経路線が筆で描くように満ち、描き終えると所要時間バッジが飛び出します。
使いどころ経路案内の結果を一目で見せるとき
stroke-dashoffsetpathLengtharia-label

<main class="stage">
<div class="rd is-demo">
<div class="rd__map">
<span class="rd__block rd__block--a" aria-hidden="true"></span>
<span class="rd__block rd__block--b" aria-hidden="true"></span>
<span class="rd__block rd__block--c" aria-hidden="true"></span>
<svg class="rd__route" viewBox="0 0 480 300" preserveAspectRatio="none" role="img" aria-label="出発地から目的地までの推定ルート、12分 3.4km">
<path class="rd__line rd__line--base" pathLength="100" vector-effect="non-scaling-stroke" d="M58 246 L58 196 L150 196 L150 128 L286 128 L286 74 L414 74"/>
<path class="rd__line rd__line--draw" pathLength="100" vector-effect="non-scaling-stroke" d="M58 246 L58 196 L150 196 L150 128 L286 128 L286 74 L414 74"/>
</svg>
<span class="rd__pin rd__pin--start" aria-hidden="true"><svg viewBox="0 0 256 256" width="24" height="24"><g fill="currentColor"><path d="M128 24a80 80 0 0 0-80 80c0 72 80 128 80 128s80-56 80-128a80 80 0 0 0-80-80m0 112a32 32 0 1 1 32-32a32 32 0 0 1-32 32" opacity=".2"/><path d="M128 64a40 40 0 1 0 40 40a40 40 0 0 0-40-40m0 64a24 24 0 1 1 24-24a24 24 0 0 1-24 24m0-112a88.1 88.1 0 0 0-88 88c0 31.4 14.51 64.68 42 96.25a254.2 254.2 0 0 0 41.45 38.3a8 8 0 0 0 9.18 0a254.2 254.2 0 0 0 41.37-38.3c27.45-31.57 42-64.85 42-96.25a88.1 88.1 0 0 0-88-88m0 206c-16.53-13-72-60.75-72-118a72 72 0 0 1 144 0c0 57.23-55.47 105-72 118"/></g></svg></span>
<span class="rd__flag" aria-hidden="true"><svg viewBox="0 0 256 256" width="20" height="20"><g fill="currentColor"><path d="M224 56v60c-19.89 17.23-38.23 18.39-56 13.48v-60c17.76 4.91 36.11 3.75 56-13.48m-120 46.52v-60C86.24 37.61 67.89 38.77 48 56v60c19.89-17.22 38.23-18.39 56-13.48v60c21.62 6 42.38 21 64 27v-60c-21.62-6-42.38-21-64-27" opacity=".2"/><path d="M227.32 48.75a8 8 0 0 0-8.56 1.25c-28 24.22-51.72 12.48-79.21-1.13C111.07 34.76 78.78 18.79 42.76 50A8 8 0 0 0 40 56v168a8 8 0 0 0 16 0v-44.23c26.79-21.16 49.87-9.75 76.45 3.41c28.49 14.09 60.77 30.06 96.79-1.13a8 8 0 0 0 2.76-6V56a8 8 0 0 0-4.68-7.25M216 71.6v40.65c-14 11.06-27 13.22-40 10.88V79.34a60.05 60.05 0 0 0 40-7.74m-56 3.76v43c-6.66-2.67-13.43-6-20.45-9.48c-8.82-4.37-18-8.91-27.55-12.18v-43c6.66 2.66 13.43 6 20.45 9.48c8.82 4.37 18.01 8.91 27.55 12.18M96 48.91v43.78a60.06 60.06 0 0 0-40 7.75V59.78c14-11.06 27-13.21 40-10.87M86.58 152A60.06 60.06 0 0 0 56 160.43v-40.65c14-11.06 27-13.22 40-10.88v43.8a65.6 65.6 0 0 0-9.42-.7m25.42 4.67v-43c6.66 2.66 13.43 6 20.45 9.48c8.82 4.37 18 8.9 27.55 12.17v43c-6.66-2.67-13.43-6-20.45-9.48c-8.82-4.37-18.01-8.9-27.55-12.17m64 26.45v-43.8a65.6 65.6 0 0 0 9.42.72a60.1 60.1 0 0 0 30.58-8.47v40.68c-14 11.06-27 13.21-40 10.87"/></g></svg></span>
<span class="rd__badge" aria-hidden="true">
<span class="rd__badge-ico"><svg viewBox="0 0 256 256" width="12" height="12"><g fill="currentColor"><path d="M234.35 129L152 152l-23 82.35a8 8 0 0 1-15.21.27l-65.28-176a8 8 0 0 1 10.12-10.16l176 65.28a8 8 0 0 1-.28 15.26" opacity=".2"/><path d="M237.33 106.21L61.41 41l-.16-.05a16 16 0 0 0-20.35 20.3a1 1 0 0 0 .05.16l65.26 175.92A15.77 15.77 0 0 0 121.28 248h.3a15.77 15.77 0 0 0 15-11.29l.06-.2l21.84-78l78-21.84l.2-.06a16 16 0 0 0 .62-30.38Zm-87.49 38.09a8 8 0 0 0-5.54 5.54l-23 82.16l-.06-.17L56 56l175.82 65.22l.16.06Z"/></g></svg></span>
<span class="rd__badge-txt">12分・3.4km</span>
</span>
</div>
<button class="rd__btn" type="button" id="rd-btn">
<span class="rd__btn-ico" aria-hidden="true"><svg viewBox="0 0 256 256" width="13" height="13"><g fill="currentColor"><path d="M234.35 129L152 152l-23 82.35a8 8 0 0 1-15.21.27l-65.28-176a8 8 0 0 1 10.12-10.16l176 65.28a8 8 0 0 1-.28 15.26" opacity=".2"/><path d="M237.33 106.21L61.41 41l-.16-.05a16 16 0 0 0-20.35 20.3a1 1 0 0 0 .05.16l65.26 175.92A15.77 15.77 0 0 0 121.28 248h.3a15.77 15.77 0 0 0 15-11.29l.06-.2l21.84-78l78-21.84l.2-.06a16 16 0 0 0 .62-30.38Zm-87.49 38.09a8 8 0 0 0-5.54 5.54l-23 82.16l-.06-.17L56 56l175.82 65.22l.16.06Z"/></g></svg></span>
<span>ルートを引き直します</span>
</button>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-base;
$easing: $ease-spring;
$color: $subject-blue;
@include stage($stage-orange);
// 가짜 지도 캔버스 — 외부 지도 API·이미지 금지. CSS 그라디언트만 (249 아홉 항목 공통 규격)
$map-land: #efe9dc;
$map-road: #fffdf7;
$map-block: #e2dac9;
$map-park: #c9e0bd;
$map-water: #bcd9ea;
.rd {
box-sizing: border-box;
width: min(480px, 88vw);
display: flex;
flex-direction: column;
gap: $sp-2;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
}
// 지도 배경은 움직이지 않는다 — 움직이는 건 경로선과 배지뿐
.rd__map {
position: relative;
overflow: hidden;
border-radius: $r-card;
width: 100%;
height: clamp(104px, 34vw, 160px);
box-shadow: $shadow-press;
background:
radial-gradient(circle at 16% 74%, $map-park 0 42px, transparent 42px),
linear-gradient(104deg, transparent 0 64%, $map-water 64% 71%, transparent 71%),
repeating-linear-gradient(90deg, $map-road 0 7px, transparent 7px 30px),
repeating-linear-gradient(0deg, $map-road 0 7px, transparent 7px 26px),
$map-land;
}
.rd__block {
position: absolute;
display: block;
background: $map-block;
border-radius: $r-xs;
}
.rd__block--a { left: 20%; top: 10%; width: 15%; height: 22%; }
.rd__block--b { left: 47%; top: 48%; width: 19%; height: 26%; }
.rd__block--c { left: 73%; top: 42%; width: 13%; height: 20%; }
// ── 경로선 ──────────────────────────────────────────────
.rd__route { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
.rd__line { fill: none; stroke-linecap: round; stroke-linejoin: round; }
.rd__line--base { stroke: rgba(23, 20, 26, .13); stroke-width: 7; }
.rd__line--draw {
stroke: $color;
stroke-width: 5;
stroke-dasharray: 100; // pathLength="100" 이라 dash 값이 곧 %
stroke-dashoffset: 100;
}
// 조작 모드에서는 transition 으로 긋는다
.rd:not(.is-demo) .rd__line--draw { transition: stroke-dashoffset 900ms $ease-out; }
.rd.is-drawn .rd__line--draw { stroke-dashoffset: 0; }
.rd__pin, .rd__flag { position: absolute; display: block; filter: drop-shadow(0 2px 3px rgba(23, 20, 26, .28)); }
.rd__pin svg, .rd__flag svg { display: block; }
.rd__pin--start { left: 12.1%; top: 82%; transform: translate(-50%, -100%); color: $color; }
.rd__flag { left: 86.3%; top: 24.7%; transform: translate(-50%, -50%); color: rgba(23, 20, 26, .82); }
// ── 도착 배지 ───────────────────────────────────────────
.rd__badge {
position: absolute;
top: $sp-2;
left: 50%;
display: inline-flex;
align-items: center;
gap: $sp-1;
padding: $sp-1 $sp-2;
border-radius: $r-pill;
background: #fff;
color: rgba(23, 20, 26, .92);
font-size: 11px;
font-weight: 700;
line-height: 14px;
white-space: nowrap;
box-shadow: $shadow-press;
transform: translate(-50%, -6px) scale(.6);
opacity: 0;
transition: transform $duration $ease-pop, opacity $duration $ease-pop;
}
.rd__badge-ico { display: inline-flex; color: $color; }
.rd.is-drawn .rd__badge {
opacity: 1;
transform: translate(-50%, 0) scale(1);
transition-delay: 860ms; // 선이 다 그어진 뒤에 튀어나온다
}
// ── 다시 그리기 버튼 ────────────────────────────────────
.rd__btn {
box-sizing: border-box;
width: 100%;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
gap: $sp-1;
padding: 0 $sp-3;
border: 0;
border-radius: $r-pill;
background: #fff;
color: rgba(23, 20, 26, .92);
font-family: inherit;
font-size: 11px;
font-weight: 700;
line-height: 14px;
cursor: pointer;
box-shadow: $shadow-press;
transition: transform $dur-quick $ease-pop;
}
.rd__btn-ico { display: inline-flex; color: $color; }
.rd__btn:hover { transform: translateY(-1px); }
.rd__btn:active { transform: translateY(1px); }
.rd__btn:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
// ── 자동 루프: 긋기 → 배지 pop → 90% 안에 배지가 접히고 선이 되감긴다 (0% 와 100% 가 같은 상태) ──
.rd.is-demo .rd__line--draw {
animation-name: rd-draw;
animation-duration: $dur-loop;
animation-timing-function: $ease-out;
animation-iteration-count: infinite;
}
@keyframes rd-draw {
0% { stroke-dashoffset: 100; }
45% { stroke-dashoffset: 0; }
72% { stroke-dashoffset: 0; }
88% { stroke-dashoffset: 100; }
100% { stroke-dashoffset: 100; }
}
.rd.is-demo .rd__badge {
animation-name: rd-badge;
animation-duration: $dur-loop;
animation-timing-function: $ease-pop;
animation-iteration-count: infinite;
}
@keyframes rd-badge {
0% { opacity: 0; transform: translate(-50%, -6px) scale(.6); }
44% { opacity: 0; transform: translate(-50%, -6px) scale(.6); }
56% { opacity: 1; transform: translate(-50%, 0) scale(1); }
72% { opacity: 1; transform: translate(-50%, 0) scale(1); }
84% { opacity: 0; transform: translate(-50%, -6px) scale(.6); }
100% { opacity: 0; transform: translate(-50%, -6px) scale(.6); }
}
// 320×200 — 무대 패딩(25.6px)을 빼면 174px 안에 들어가야 한다. 지도 108.8 + 간격 0 + 버튼 28
@media (max-width: 340px) {
.rd { gap: 0; }
.rd__btn { margin-top: $sp-1; }
}
@media (prefers-reduced-motion: reduce) {
.rd.is-demo .rd__line--draw, .rd.is-demo .rd__badge { animation: none !important; }
.rd__line--draw, .rd__badge, .rd__btn { transition: none; }
.rd__line--draw { stroke-dashoffset: 0; }
.rd__badge { opacity: 1; transform: translate(-50%, 0) scale(1); }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ff4d1f;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.rd {
box-sizing: border-box;
width: min(480px, 88vw);
display: flex;
flex-direction: column;
gap: 8px;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
}
.rd__map {
position: relative;
overflow: hidden;
border-radius: 16px;
width: 100%;
height: clamp(104px, 34vw, 160px);
box-shadow: 0 6px 12px rgba(23, 20, 26, 0.32);
background: radial-gradient(circle at 16% 74%, #c9e0bd 0 42px, transparent 42px), linear-gradient(104deg, transparent 0 64%, #bcd9ea 64% 71%, transparent 71%), repeating-linear-gradient(90deg, #fffdf7 0 7px, transparent 7px 30px), repeating-linear-gradient(0deg, #fffdf7 0 7px, transparent 7px 26px), #efe9dc;
}
.rd__block {
position: absolute;
display: block;
background: #e2dac9;
border-radius: 4px;
}
.rd__block--a {
left: 20%;
top: 10%;
width: 15%;
height: 22%;
}
.rd__block--b {
left: 47%;
top: 48%;
width: 19%;
height: 26%;
}
.rd__block--c {
left: 73%;
top: 42%;
width: 13%;
height: 20%;
}
.rd__route {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.rd__line {
fill: none;
stroke-linecap: round;
stroke-linejoin: round;
}
.rd__line--base {
stroke: rgba(23, 20, 26, 0.13);
stroke-width: 7;
}
.rd__line--draw {
stroke: #2f6df6;
stroke-width: 5;
stroke-dasharray: 100;
stroke-dashoffset: 100;
}
.rd:not(.is-demo) .rd__line--draw {
transition: stroke-dashoffset 900ms ease-out;
}
.rd.is-drawn .rd__line--draw {
stroke-dashoffset: 0;
}
.rd__pin, .rd__flag {
position: absolute;
display: block;
filter: drop-shadow(0 2px 3px rgba(23, 20, 26, 0.28));
}
.rd__pin svg, .rd__flag svg {
display: block;
}
.rd__pin--start {
left: 12.1%;
top: 82%;
transform: translate(-50%, -100%);
color: #2f6df6;
}
.rd__flag {
left: 86.3%;
top: 24.7%;
transform: translate(-50%, -50%);
color: rgba(23, 20, 26, 0.82);
}
.rd__badge {
position: absolute;
top: 8px;
left: 50%;
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 8px;
border-radius: 999px;
background: #fff;
color: rgba(23, 20, 26, 0.92);
font-size: 11px;
font-weight: 700;
line-height: 14px;
white-space: nowrap;
box-shadow: 0 6px 12px rgba(23, 20, 26, 0.32);
transform: translate(-50%, -6px) scale(0.6);
opacity: 0;
transition: transform 300ms cubic-bezier(0.2, 1.4, 0.4, 1), opacity 300ms cubic-bezier(0.2, 1.4, 0.4, 1);
}
.rd__badge-ico {
display: inline-flex;
color: #2f6df6;
}
.rd.is-drawn .rd__badge {
opacity: 1;
transform: translate(-50%, 0) scale(1);
transition-delay: 860ms;
}
.rd__btn {
box-sizing: border-box;
width: 100%;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
padding: 0 12px;
border: 0;
border-radius: 999px;
background: #fff;
color: rgba(23, 20, 26, 0.92);
font-family: inherit;
font-size: 11px;
font-weight: 700;
line-height: 14px;
cursor: pointer;
box-shadow: 0 6px 12px rgba(23, 20, 26, 0.32);
transition: transform 220ms cubic-bezier(0.2, 1.4, 0.4, 1);
}
.rd__btn-ico {
display: inline-flex;
color: #2f6df6;
}
.rd__btn:hover {
transform: translateY(-1px);
}
.rd__btn:active {
transform: translateY(1px);
}
.rd__btn:focus-visible {
outline: 2px solid #fff;
outline-offset: 2px;
}
.rd.is-demo .rd__line--draw {
animation-name: rd-draw;
animation-duration: 2s;
animation-timing-function: ease-out;
animation-iteration-count: infinite;
}
@keyframes rd-draw {
0% {
stroke-dashoffset: 100;
}
45% {
stroke-dashoffset: 0;
}
72% {
stroke-dashoffset: 0;
}
88% {
stroke-dashoffset: 100;
}
100% {
stroke-dashoffset: 100;
}
}
.rd.is-demo .rd__badge {
animation-name: rd-badge;
animation-duration: 2s;
animation-timing-function: cubic-bezier(0.2, 1.4, 0.4, 1);
animation-iteration-count: infinite;
}
@keyframes rd-badge {
0% {
opacity: 0;
transform: translate(-50%, -6px) scale(0.6);
}
44% {
opacity: 0;
transform: translate(-50%, -6px) scale(0.6);
}
56% {
opacity: 1;
transform: translate(-50%, 0) scale(1);
}
72% {
opacity: 1;
transform: translate(-50%, 0) scale(1);
}
84% {
opacity: 0;
transform: translate(-50%, -6px) scale(0.6);
}
100% {
opacity: 0;
transform: translate(-50%, -6px) scale(0.6);
}
}
@media (max-width: 340px) {
.rd {
gap: 0;
}
.rd__btn {
margin-top: 4px;
}
}
@media (prefers-reduced-motion: reduce) {
.rd.is-demo .rd__line--draw, .rd.is-demo .rd__badge {
animation: none !important;
}
.rd__line--draw, .rd__badge, .rd__btn {
transition: none;
}
.rd__line--draw {
stroke-dashoffset: 0;
}
.rd__badge {
opacity: 1;
transform: translate(-50%, 0) scale(1);
}
}// 실제 동작: 버튼(마우스 클릭 · Enter/Space)으로 경로선을 0 부터 다시 긋고, 다 그으면 배지를 띄운다.
var root = document.querySelector('.rd');
var btn = document.getElementById('rd-btn');
var line = document.querySelector('.rd__line--draw');
function redraw() {
root.classList.remove('is-demo'); // 사람이 조작하면 자동 루프를 끈다 (237 선례)
root.classList.remove('is-drawn');
void line.getBoundingClientRect(); // 되감긴 상태를 한 프레임 확정시킨 뒤 다시 긋는다
requestAnimationFrame(function () {
requestAnimationFrame(function () { root.classList.add('is-drawn'); });
});
}
btn.addEventListener('click', redraw);
btn.addEventListener('keydown', function (e) {
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); redraw(); }
});import { useState } from "react";
import type { CSSProperties } from "react";
import styles from "./RouteDraw.module.scss";
export type RouteDrawProps = {
/** 경로 배지에 쓸 소요시간·거리 */
eta?: string;
/** 경로 SVG 의 스크린리더 설명 */
routeLabel?: string;
/** 다시 그리기 버튼 글자 */
buttonLabel?: string;
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
type KeyEvt = { key: string; preventDefault: () => void };
// pathLength="100" 으로 길이를 정규화해 dash 값이 곧 진행률 % 가 되게 한다
const ROUTE = "M58 246 L58 196 L150 196 L150 128 L286 128 L286 74 L414 74";
export default function RouteDraw({
eta = "12 min · 3.4 km",
routeLabel = "Route from start to destination, 12 min 3.4 km",
buttonLabel = "Draw the route again",
duration = 300,
easing = "cubic-bezier(.2,.8,.2,1)",
color = "#2f6df6",
}: RouteDrawProps) {
const [demo, setDemo] = useState(true);
const [drawn, setDrawn] = useState(false);
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
function redraw() {
setDemo(false); // 사람이 조작하면 자동 루프를 끈다
setDrawn(false);
requestAnimationFrame(() => requestAnimationFrame(() => setDrawn(true)));
}
return (
<div className={`${styles.rd} ${demo ? styles.isDemo : ""} ${drawn ? styles.isDrawn : ""}`} style={vars}>
<div className={styles.map}>
<span className={`${styles.block} ${styles.blockA}`} aria-hidden="true" />
<span className={`${styles.block} ${styles.blockB}`} aria-hidden="true" />
<span className={`${styles.block} ${styles.blockC}`} aria-hidden="true" />
<svg className={styles.route} viewBox="0 0 480 300" preserveAspectRatio="none" role="img" aria-label={routeLabel}>
<path className={`${styles.line} ${styles.lineBase}`} pathLength={100} vectorEffect="non-scaling-stroke" d={ROUTE} />
<path className={`${styles.line} ${styles.lineDraw}`} pathLength={100} vectorEffect="non-scaling-stroke" d={ROUTE} />
</svg>
<span className={`${styles.pin} ${styles.pinStart}`} aria-hidden="true">
<svg viewBox="0 0 256 256" width="24" height="24"><g fill="currentColor"><path d="M128 24a80 80 0 0 0-80 80c0 72 80 128 80 128s80-56 80-128a80 80 0 0 0-80-80m0 112a32 32 0 1 1 32-32a32 32 0 0 1-32 32" opacity=".2" /><path d="M128 64a40 40 0 1 0 40 40a40 40 0 0 0-40-40m0 64a24 24 0 1 1 24-24a24 24 0 0 1-24 24m0-112a88.1 88.1 0 0 0-88 88c0 31.4 14.51 64.68 42 96.25a254.2 254.2 0 0 0 41.45 38.3a8 8 0 0 0 9.18 0a254.2 254.2 0 0 0 41.37-38.3c27.45-31.57 42-64.85 42-96.25a88.1 88.1 0 0 0-88-88m0 206c-16.53-13-72-60.75-72-118a72 72 0 0 1 144 0c0 57.23-55.47 105-72 118" /></g></svg>
</span>
<span className={styles.flag} aria-hidden="true">
<svg viewBox="0 0 256 256" width="20" height="20"><g fill="currentColor"><path d="M224 56v60c-19.89 17.23-38.23 18.39-56 13.48v-60c17.76 4.91 36.11 3.75 56-13.48m-120 46.52v-60C86.24 37.61 67.89 38.77 48 56v60c19.89-17.22 38.23-18.39 56-13.48v60c21.62 6 42.38 21 64 27v-60c-21.62-6-42.38-21-64-27" opacity=".2" /><path d="M227.32 48.75a8 8 0 0 0-8.56 1.25c-28 24.22-51.72 12.48-79.21-1.13C111.07 34.76 78.78 18.79 42.76 50A8 8 0 0 0 40 56v168a8 8 0 0 0 16 0v-44.23c26.79-21.16 49.87-9.75 76.45 3.41c28.49 14.09 60.77 30.06 96.79-1.13a8 8 0 0 0 2.76-6V56a8 8 0 0 0-4.68-7.25M216 71.6v40.65c-14 11.06-27 13.22-40 10.88V79.34a60.05 60.05 0 0 0 40-7.74m-56 3.76v43c-6.66-2.67-13.43-6-20.45-9.48c-8.82-4.37-18-8.91-27.55-12.18v-43c6.66 2.66 13.43 6 20.45 9.48c8.82 4.37 18.01 8.91 27.55 12.18M96 48.91v43.78a60.06 60.06 0 0 0-40 7.75V59.78c14-11.06 27-13.21 40-10.87M86.58 152A60.06 60.06 0 0 0 56 160.43v-40.65c14-11.06 27-13.22 40-10.88v43.8a65.6 65.6 0 0 0-9.42-.7m25.42 4.67v-43c6.66 2.66 13.43 6 20.45 9.48c8.82 4.37 18 8.9 27.55 12.17v43c-6.66-2.67-13.43-6-20.45-9.48c-8.82-4.37-18.01-8.9-27.55-12.17m64 26.45v-43.8a65.6 65.6 0 0 0 9.42.72a60.1 60.1 0 0 0 30.58-8.47v40.68c-14 11.06-27 13.21-40 10.87" /></g></svg>
</span>
<span className={styles.badge} aria-hidden="true">
<span className={styles.badgeIco}>
<svg viewBox="0 0 256 256" width="12" height="12"><g fill="currentColor"><path d="M234.35 129L152 152l-23 82.35a8 8 0 0 1-15.21.27l-65.28-176a8 8 0 0 1 10.12-10.16l176 65.28a8 8 0 0 1-.28 15.26" opacity=".2" /><path d="M237.33 106.21L61.41 41l-.16-.05a16 16 0 0 0-20.35 20.3a1 1 0 0 0 .05.16l65.26 175.92A15.77 15.77 0 0 0 121.28 248h.3a15.77 15.77 0 0 0 15-11.29l.06-.2l21.84-78l78-21.84l.2-.06a16 16 0 0 0 .62-30.38Zm-87.49 38.09a8 8 0 0 0-5.54 5.54l-23 82.16l-.06-.17L56 56l175.82 65.22l.16.06Z" /></g></svg>
</span>
<span>{eta}</span>
</span>
</div>
<button
type="button"
className={styles.btn}
onClick={redraw}
onKeyDown={(e: KeyEvt) => {
if (e.key === "Enter" || e.key === " ") { e.preventDefault(); redraw(); }
}}
>
<span className={styles.btnIco} aria-hidden="true">
<svg viewBox="0 0 256 256" width="13" height="13"><g fill="currentColor"><path d="M234.35 129L152 152l-23 82.35a8 8 0 0 1-15.21.27l-65.28-176a8 8 0 0 1 10.12-10.16l176 65.28a8 8 0 0 1-.28 15.26" opacity=".2" /><path d="M237.33 106.21L61.41 41l-.16-.05a16 16 0 0 0-20.35 20.3a1 1 0 0 0 .05.16l65.26 175.92A15.77 15.77 0 0 0 121.28 248h.3a15.77 15.77 0 0 0 15-11.29l.06-.2l21.84-78l78-21.84l.2-.06a16 16 0 0 0 .62-30.38Zm-87.49 38.09a8 8 0 0 0-5.54 5.54l-23 82.16l-.06-.17L56 56l175.82 65.22l.16.06Z" /></g></svg>
</span>
<span>{buttonLabel}</span>
</button>
</div>
);
}07 描かれていく経路線 — 計測 累積面積 3.528% · 動いたフレーム 16/23GODRICH オリジナル · SCSS と React は zip に同梱 · 地図 ui デザイン 9選 — クリックでピンが落ちる位置選択
このデモが属する記事
地図 ui デザイン 9選 — クリックでピンが落ちる位置選択地図 ui デザインとは、配達先や待ち合わせ場所を「住所を書かせる」のではなく地図の上で指して決めてもらう画面の作りです。9つとも地図 API を使わず、CSS で描いた盤の上だけで動きます。 この記事のデモをすべて見る

