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