react+faker+useWebAnimations实现销售数据卡片选项卡曲线图效果代码
代码语言:html
所属分类:选项卡
代码描述:react+faker+useWebAnimations实现销售数据卡片选项卡曲线图效果代码
代码标签: react faker useWebAnimations 销售 数据 卡片 选项卡 曲线图
下面为部分代码预览,完整代码请点击下载或在bfwstudio webide中打开
<!DOCTYPE html> <html lang="en" > <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"> <style> * { border: 0; box-sizing: border-box; margin: 0; padding: 0; } :root { --hue: 223; --hue2: 253; --hue-negative: 8; --hue-positive: 133; --bg: hsl(var(--hue),10%,90%); --fg: hsl(var(--hue),10%,10%); --primary: hsl(var(--hue),90%,50%); --primary-t: hsla(var(--hue),90%,50%,0); --negative: hsl(var(--hue-negative),90%,45%); --positive: hsl(var(--hue-positive),90%,27.5%); --trans-dur: 0.3s; --trans-timing: cubic-bezier(0.65,0,0.35,1); font-size: clamp(0.75rem,0.65rem + 0.5vw,1.25rem); } body, button { color: var(--fg); font: 1em/1.5 -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, sans-serif; } body { background-color: var(--bg); display: flex; height: 100vh; transition: background-color var(--trans-dur), color var(--trans-dur); } button { cursor: pointer; outline: transparent; transition: background-color var(--trans-dur), box-shadow calc(var(--trans-dur) / 2), color var(--trans-dur); -webkit-appearance: none; appearance: none; -webkit-tap-highlight-color: transparent; } main { display: grid; place-items: center; overflow-x: hidden; padding: 1.5em 0; width: 100vw; height: 100vh; } main > svg { position: fixed; } small { font-size: 0.875em; } .bar-graph, .line-graph { display: flex; height: 9em; } .bar-graph__label, .line-graph__label { font-size: 0.875em; text-align: center; } .bar-graph__svg, .line-graph__svg { display: block; } .bar-graph line, .line-graph line { transition: stroke var(--trans-dur), stroke-dashoffset var(--trans-dur) var(--trans-timing); } .bar-graph__bar { display: flex; gap: 1.25em; flex-direction: column; align-items: center; width: 100%; } .bar-graph__svg { width: 0.5em; height: 6.5em; } .bar-graph__track { stroke: hsl(var(--hue), 10%, 90%); } .line-graph { position: relative; align-items: flex-end; } .line-graph__glow { animation: slide-in 0.5s linear forwards; } .line-graph__label { width: 100%; } .line-graph__point { background-color: hsla(var(--hue2), 90%, 70%, 0); border-radius: 50%; box-shadow: 0 0 0 0 hsla(var(--hue2), 90%, 70%, 0.3); position: absolute; top: 0; left: 0; width: 1em; height: 1em; transform: translate(-50%, -50%); transition: background-color var(--trans-dur), box-shadow var(--trans-dur); } .line-graph__point:focus-visible, .line-graph__point:hover { background-color: hsla(var(--hue2), 90%, 70%, 1); } .line-graph__point:focus-visible { box-shadow: 0 0 0 0.25em rgba(139, 110, 247, 0.3); } [dir=rtl] .line-graph__point { transform: translate(50%, -50%); } .line-graph__svg, .line-graph__points { position: absolute; top: 0; left: 0; width: 100%; } .line-graph__svg { height: auto; } [dir=rtl] .line-graph__svg { transform: scaleX(-1); } .line-graph__points { aspect-ratio: 40/13; } .change-negative, .change-positive { transition: color var(--trans-dur); } .change-negative { color: var(--negative); } .change-positive { color: var(--positive); } .icon { display: block; width: 1em; height: 1em; } .saas { background-color: hsl(var(--hue), 10%, 97.5%); border-radius: 1.25em; box-shadow: 0 0.25em 0.75em rgba(0, 0, 0, 0.1); padding: 2.5em 1.75em 1.75em; width: 25.5em; height: 37.5em; transition: background-color var(--trans-dur), box-shadow var(--trans-dur); } .saas__block { background-color: white; border-radius: 1em; box-shadow: 0 0.25em 0.5em rgba(0, 0, 0, 0.1); margin-bottom: 1em; padding: 1.5em; transition: background-color var(--trans-dur), box-shadow var(--trans-dur); } .saas__button { background-color: hsl(var(--hue), 10%, 95%); border-radius: 1rem; box-shadow: 0 0 0 2px var(--primary-t); display: flex; gap: 0.375rem; align-items: center; font-size: 0.75em; font-weight: 700; line-height: 2; padding: 0 1em; } .saas__button:disabled { cursor: not-allowed; opacity: 0.5; } .saas__button:focus-visible { box-shadow: 0 0 0 2px var(--primary); } .saas__button:not(:disabled):hover { background-color: hsl(var(--hue), 10%, 85%); } .saas__column { width: 100%; } .saas__columns { display: flex; align-items: center; gap: 1.25em; } .saas__label { color: hsl(var(--hue), 10%, 45%); font-size: 0.625em; font-weight: 600; line-height: 1.6; text-transform: uppercase; transition: color var(--trans-dur); } .saas__sep { border-top: 1px solid hsl(var(--hue), 10%, 90%); margin: 1.25em 0; transition: border-color var(--trans-dur); } .saas__stat { margin-bottom: 1.5em; position: relative; } .saas__stat-change { display: flex; align-items: center; gap: 0.375rem; font-size: 0.75em; font-weight: 700; position: absolute; top: 0; right: 0; } [dir=rtl] .saas__stat-change { right: auto; left: 0; } .saas__stat-change .icon { width: 1rem; height: 1rem; } .saas__tip { color: hsl(var(--hue), 10%, 40%); font-size: 0.75em; line-height: 2; text-align: center; transition: color var(--trans-dur); } .saas__title { display: flex; gap: 0.5rem; align-items: center; font-size: 1.25em; font-weight: 600; line-height: 1; margin-bottom: 2rem; } .saas__user-avatar { border-radius: 50%; box-shadow: 0 0 0 0.125em white; color: black; display: grid; flex-shrink: 0; place-items: center; width: 2.5em; height: 2.5em; transition: box-shadow var(--trans-dur); } .saas__user-avatar--lg { box-shadow: none !important; font-size: 4.5em; width: 7rem; height: 7rem; } .saas__user-avatar-list, .saas__user-avatar-row { display: flex; } .saas__user-avatar-list { height: 2.5em; } .saas__user-avatar-list .saas__user-avatar { margin-inline-end: -1em; } .saas__user-avatar-row { justify-content: space-between; align-items: center; } .saas__user-details { width: 100%; } .saas__user-empty { color: hsl(var(--hue), 10%, 50%); display: flex; gap: 0.75em; flex-direction: column; justify-content: center; align-items: center; height: 7em; } .saas__user-empty .icon { color: hsl(var(--hue), 10%, 80%); font-size: 3em; line-height: 1; transition: color var(--trans-dur); } .saas__user-info { min-width: 0; } .saas__value { display: flex; gap: 0.5rem; align-items: center; font-size: 1.25em; font-weight: 500; line-height: 1.4; } .saas__value--lg { font-size: 2.75em; } .saas__value--truncated { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .saas__value + .saas__label { margin-top: 1rem; } .segmented { background-color: hsl(var(--hue), 10%, 90%); border-radius: 2em; display: flex; justify-content: center; align-items: center; margin: 0 auto 1em auto; position: relative; width: 100%; transition: background-color var(--trans-dur); } .segmented__bg { background-color: white; border-radius: inherit; box-shadow: 0 0.25em 0.5em rgba(0, 0, 0, 0.1); position: absolute; top: 0.25em; left: 0.25em; width: calc(100% - 0.5em); height: calc(100% - 0.5em); transition: background-color var(--trans-dur), transform var(--trans-dur) var(--trans-timing); } [dir=rtl] .segmented__bg { right: 0.25em; left: auto; } .segmented__button { background-color: transparent; border-radius: 2rem; box-shadow: 0 0 0 3px var(--primary-t); color: hsl(var(--hue), 10%, 40%); cursor: pointer; font-size: 0.875em; font-weight: 600; line-height: 1.7; padding: 0.5rem; position: relative; transition: box-shadow var(--trans-dur), color var(--trans-dur); width: 100%; z-index: 1; } .segmented__button:focus-visible { box-shadow: 0 0 0 3px var(--primary); } .segmented__button[aria-selected=true] { color: var(--fg); } .segmented__button[aria-selected=false]:focus-visible, .segmented__button[aria-selected=false]:hover { color: hsl(var(--hue), 10%, 20%); } /* Dark theme */ @media (prefers-color-scheme: dark) { :root { --bg: hsl(var(--hue),0%,10%); --fg: hsl(var(--hue),0%,90%); --negative: hsl(var(--hue-negative),90%,67.5%); --positive: hsl(var(--hue-positive),90%,50%); } .bar-graph__track { stroke: hsl(var(--hue), 10%, 30%); } .saas { background-color: hsl(var(--hue), 10%, 17.5%); } .saas__block { background-color: hsl(var(--hue), 10%, 20%); } .saas__button { background-color: hsl(var(--hue), 10%, 15%); } .saas__button:not(:disabled):hover { background-color: hsl(var(--hue), 10%, 5%); } .saas__label { color: hsl(var(--hue), 10%, 65%); } .saas__sep { border-color: hsl(var(--hue), 10%, 30%); } .saas__tip { color: hsl(var(--hue), 10%, 70%); } .saas__user-avatar { box-shadow: 0 0 0 0.125em hsl(var(--hue), 10%, 20%); } .saas__user-empty .icon { color: hsl(var(--hue), 10%, 40%); } .segmented { background-color: hsl(var(--hue), 10%, 10%); } .segmented__bg { background-color: hsl(var(--hue), 10%, 25%); } .segmented__button { color: hsl(var(--hue), 10%, 55%); } .segmented__button[aria-selected=false]:focus-visible, .segmented__button[aria-selected=false]:hover { color: hsl(var(--hue), 10%, 75%); } } /* Animations */ @keyframes slide-in { from { transform: translate(-200px, 0); } to { transform: translate(0, 0); } } </style> </head> <body translate="no"> <div id="root"></div> <script type="module"> import React, { StrictMode, Suspense, useState } from "https://esm.sh/react"; import { createRoot } from "https://esm.sh/react-dom/client"; import { faker } from "https://esm.sh/@faker-js/faker"; import useWebAnimations from "https://esm.sh/@wellyshen/use-web-animations"; createRoot(document.getElementById("root")).render(React.createElement(StrictMode, null, React.createElement("main", null, React.createElement(IconSprites, null), React.createElement(Suspense, { fallback: React.createElement("div", null, "Loading\u2026") }, React.createElement(SaaSWidget, { userData: fakeUserData(), userTarget: 150, dailyPurchaseTarget: 5000, monthlyPurchaseTarget: 100000 }))))); function fakeUserData() { const data = []; const emojiList = { male: ["👱🏻♂️", "👨🏻", "👨🏻🦳", "🧔🏽♂️", "👨🏾", "👨🏿🦱", "👨🏿🦲"], female: ["👱🏻♀️", "👩🏻", "👩🏻🦳", "👩🏽", "👩🏽🦱", "👧🏿", "👩🏿"] }; const now = new Date(); const year = now.getFullYear(); const month = now.getMonth(); let months = 6; while (months--) { const startBound = new Date(year, month - months, 1); const endBound = Math.min(new Date(year, month - (months - 1), 0).getTime(), now.getTime()); let userCount = faker.number.int({ min: 80, max: 150 }); while (userCount--) { const sex = faker.person.sex(); const emojisBySex = emojiList[sex]; // user const user = { registered: faker.date.between({ from: startBound, to: endBound }), name: `${faker.person.firstName(sex)} ${faker.person.lastName()}`, emoji: emojisBySex[faker.number.int({ max: emojisBySex.length - 1 })], color: `hsl(${faker.number.int({ min: 0, max: 359 })},90%,70%)`, purchases: [] }; // user’s purchases let purchaseCount = faker.number.int({ min: 1, max: 10 }); while (purchaseCount--) { user.purchases.push({ date: user.registered, type: faker.datatype.boolean() ? "digital" : "physical", value: faker.number.int({ min: 1, max: 150, multipleOf: 5 }) - 0.01 }); } data.push(user); } } return data; } function BarGraph({ dataSet, isCurrency = false }) { return (React.createElement("div", { className: "bar-graph" }, dataSet.map((set, i) => (React.createElement(BarGraphBar, { key: i, value: set.value, maxValue: set.maxValue, label: set.label, isCurrency: isCurrency }))))); } function BarGraphBar({ value, maxValue, label = "", isCurrency = false }) { const valueDisplayed = isCurrency ? new Intl.NumberFormat(LOCALE, { style: "currency", currency: CURRENCY }).format(value) : `${value}`; const lineLength = 60; const dashArray = `${lineLength} ${lineLength + 1}`; const offset = (1 - Math.min(value / maxValue, 1)) * -lineLength; const { ref } = useWebAnimations({ keyframes: { strokeDashoffset: [-lineLength, offset] }, animationOptions: { duration: 500, easing: "cubic-bezier(0.65,0,0.35,1)", fill: "forwards" }, }); return (React.createElement("div", { className: "bar-graph__bar" }, React.createElement("svg", { className: "bar-graph__svg", viewBox: "0 0 5 65", width: "5px", height: "65px", role: "img" }, React.createElement("title", null, valueDisplayed), React.createElement("defs", null, React.createElement("linearGradient", { id: "bar-grad", x1: "0", y1: "0", x2: "0", y2: "1" }, React.createElement("stop", { offset: "0", stopColor: "hsl(253,90%,80%)" }), React.createElement("stop", { offset: "1", stopColor: "hsl(253,90%,60%)" }))), React.createElement("g", { strokeLinecap: "round", strokeWidth: "5" }, React.createElement("line", { className: "bar-graph__track", x1: "2.5", y1: "2.5", x2: "2.5", y2: "62.5" }), React.createElement("line", { stroke: "url(#bar-grad)", x1: "2.5", y1: "2.5", x2: "2.51", y2: "62.5", strokeDasharray: dashArray, strokeDashoffset: offset, ref: ref }))), React.createElement("span", { className: "bar-graph__label" }, label))); } function Icon({ icon }) { return (React.createElement("svg", { className: "icon", width: "16px", height: "16px", "aria-hidden": "true" }, React.createElement("use", { href: `#${icon}` }))); } function IconSprites() { return (React.createElement("svg", { width: "0", height: "0", "aria-hidden": "true" }, React.createElement("symbol", { id: "line-graph", viewBox: "0 0 24 24" }, React.createElement("g", { fill: "none", stroke: "currentcolor", strokeWidth: "2" }, React.createElement("rect", { stroke: "hsla(0,0%,50%,0.5)", rx: "4", ry: "4", x: "2", y: "2", width: "20", height: "20" }), React.createElement("polyline", { points: "6 15,11 11,13 13,18 9" }))), React.createElement("symbol", { id: "arrow-right", viewBox: "0 0 24 24" }, React.createElement("g", { fill: "none", stroke: "currentcolor", strokeWidth: "2" }, React.createElement("polyline", { points: "12 2,22 12,12 22" }), React.createElement("polyline", { points: "2 12,22 12" }))), React.createElement("symbol", { id: "arrow-left", viewBox: "0 0 24 24" }, React.createElement("g", { fill: "none", stroke: "currentcolor", strokeWidth: "2" }, React.createElement("polyline", { points: "12 2,2 12,12 22" }), React.createElement("polyline", { points: "2 12,22 12" }))), React.createElement("symbol", { id: "arrow-up-circle", viewBox: "0 0 24 24" }, React.createElement("circle", { fill: "currentcolor", r: "12", cx: "12", cy: "12", opacity: "0.2" }), React.createElement("g", { fill: "none", stroke: "currentcolor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2" }, React.createElement("polyline", { points: "6 12,12 6,18 12" }), React.createElement("polyline", { points: "12 6,12 18" }))), React.createElement("symbol", { id: "arrow-down-circle", viewBox: "0 0 24 24" }, React.createElement("circle", { fill: "currentcolor", r: "12", cx: "12", cy: "12", opacity: "0.2" }), React.createElement("g", { fill: "none", stroke: "currentcolor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2" }, React.createElement("polyline", { points: "6 12,12 18,18 12" }), React.createElement("polyline", { points: "12 6,12 18" }))), React.createElement("symbol", { id: "user", viewBox: "0 0 16 16" }, React.createElement("g", { fill: "currentcolor" }, React.createElement("path", { d: "M8 7C9.65685 7 11 5.65685 11 4C11 2.34315 9.65685 1 8 1C6.34315 1 5 2.34315 5 4C5 5.65685 6.34315 7 8 7Z" }), React.createElement("path", { d: "M14 12C14 10.3431 12.6569 9 11 9H5C3.34315 9 2 10.3431 2 12V15H14V12Z" }))))); } function LineGraph({ dataSet, maxValue, labels = [], isCurrency = false }) { const isRTL = document.dir === "rtl"; const width = 200; const height = 65; // data set values and labels are separate arrays in case there should be more points than labels const labelCount = labels.length || 1; const under2Labels = labelCount < 2; const xStart = under2Labels ? 2 : width / (labelCount * 2); const xEnd = width - xStart; const xDistance = width - (under2Labels ? 4 : width / labelCount); const yStart = 2; const yEnd = 63; const yDistance = yEnd - yStart; const points = []; // add the points dataSet.forEach((value, i) => { const x = Math.round(xStart + xDistance * (i / (Math.max(dataSet.length - 1, 1)))); const y = Math.round(yStart + yDistance * (1 - value / maxValue)); points.push([x, y]); }); // convert the point array to a string to use for the polylines const pointsToString = points.map(point => point.join(" ")).join(","); const bottomCorners = [ [Math.round(xEnd), height], [Math.round(xStart), height] ]; const fillPoints = [...points, ...bottomCorners]; const fillPointsToString = fillPoints.map(point => point.join(" ")).join(","); const lineLength = totalDistance(points); const offset = 0; const { ref } = useWebAnimations({ keyframes: { strokeDashoffset: [lineLength, offset] }, animationOptions: { duration: 500, easing: "linear", fill: "forwards" }, }); /** * Get the combined distance of an array of coordinate pairs. * @param points Array of coordinate pairs */ function totalDistance(points) { let total = 0; for (let i = 0; i < points.length - 1; i++) { const [x1, y1] = points[i]; const [x2, y2] = points[i + 1]; total += Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2); } return +total.toFixed(2); } return (React.createElement("div", { className: "line-graph" }, React.createElement("svg", { className: "line-graph__svg", viewBox: `0 0 ${width} ${height}`, width: `${width}px`, height: `${height}px`, role: "img", "aria-label": `Line graph displaying data f.........完整代码请登录后点击上方下载按钮下载查看
网友评论0