Skip to content

炉石传说排队界面模拟

示例

0鼠王子侏儒七怒汉怪盗军团探险者协会迦拉克隆后援团劫匪霸主癫狂的谋士咖啡店路人天才神童战略指挥家派对捣蛋鬼卖糖术神潜行开锁者柔情信仰战德鲁伊阿缺五棍萨满圣光流浪者暴怒猎人有爱牧师恶魔劣手魔幻唤魔师泰坦巨神迪亚波罗!!砰砰技师地精状侏儒低语的上古之神污手党徒初阶探险者试炼冠军丧气的狗头人竞技场大神怪盗喽罗庆典煞星探险佣兵暴雪研发人员通灵学园学生阴郁的温西尔哈斯的表亲血滴铠甲死亡骑士扫地僧萨齐米尔豪斯·法力风暴派对爱好者行走的活段子气球杀手传奇独演者炎灿法师守汪联赛战队炉石开发团队部落勇士联盟勇士上古之神伊利达雷风行者姐妹战棋爱好者七个小矮人 一串鱼人你的老铁未知玩家调酒师鲍勃的朋友饥饿游戏玩家健身七子云玩家培根修士恶魔领主动物园园长机械工程师鱼人先知鼠王子侏儒七怒汉怪盗军团探险者协会迦拉克隆后援团劫匪霸主癫狂的谋士咖啡店路人天才神童战略指挥家派对捣蛋鬼卖糖术神潜行开锁者柔情信仰战德鲁伊阿缺五棍萨满圣光流浪者暴怒猎人有爱牧师恶魔劣手魔幻唤魔师泰坦巨神迪亚波罗!!砰砰技师地精状侏儒低语的上古之神污手党徒初阶探险者试炼冠军丧气的狗头人竞技场大神怪盗喽罗庆典煞星探险佣兵暴雪研发人员通灵学园学生阴郁的温西尔哈斯的表亲血滴铠甲死亡骑士扫地僧萨齐米尔豪斯·法力风暴派对爱好者行走的活段子气球杀手传奇独演者炎灿法师守汪联赛战队炉石开发团队部落勇士联盟勇士上古之神伊利达雷风行者姐妹战棋爱好者七个小矮人 一串鱼人你的老铁未知玩家调酒师鲍勃的朋友饥饿游戏玩家健身七子云玩家培根修士恶魔领主动物园园长机械工程师鱼人先知
bg


DANGER

参考 https://stackoverflow.com/a/7540459/15694679

该示例包含许多看起来可以运行的 hack,请不要在正式环境中使用此代码,仅供学习参考。

点击查看代码
vue
<script setup>
import { onMounted, ref } from "vue";
import MatchMaker_phone from "../assets/MatchMaker_phone.png";
import MatchingSpinnerTiles from "../assets/MatchingSpinnerTiles1.png";
import GBJenLei from "../assets/GBJenLei-Medium.woff2";

const rollerList = [
  "派对捣蛋鬼",
  "卖糖术神",
  "潜行开锁者",
  "柔情信仰战",
  "德鲁伊阿缺",
  "五棍萨满",
  "圣光流浪者",
  "暴怒猎人",
  "有爱牧师",
  "恶魔劣手",
  "魔幻唤魔师",
  "泰坦巨神",
  "迪亚波罗!!",
  "砰砰技师",
  "地精状侏儒",
  "低语的上古之神",
  "污手党徒",
  "初阶探险者",
  "试炼冠军",
  "丧气的狗头人",
  "竞技场大神",
  "怪盗喽罗",
  "庆典煞星",
  "探险佣兵",
  "暴雪研发人员",
  "通灵学园学生",
  "阴郁的温西尔",
  "哈斯的表亲",
  "血滴铠甲死亡骑士",
  "扫地僧",
  "萨齐",
  "米尔豪斯·法力风暴",
  "派对爱好者",
  "行走的活段子",
  "气球杀手",
  "传奇独演者",
  "炎灿法师",
  "守汪联赛战队",
  "炉石开发团队",
  "部落勇士",
  "联盟勇士",
  "上古之神",
  "伊利达雷",
  "风行者姐妹",
  "战棋爱好者",
  "七个小矮人",
  "豪勇七巨龙",
  "一串鱼人",
  "鼠王子",
  "侏儒七怒汉",
  "怪盗军团",
  "探险者协会",
  "迦拉克隆后援团",
  "劫匪霸主",
  "癫狂的谋士",
  "咖啡店路人",
  "天才神童",
  "战略指挥家",
  "你的老铁",
  "未知玩家",
  "调酒师鲍勃的朋友",
  "饥饿游戏玩家",
  "健身七子",
  "云玩家",
  "培根修士",
  "恶魔领主",
  "动物园园长",
  "机械工程师",
  "鱼人先知",
];
const digitToChar = ref(
  rollerList.reduce((acc, cur, index) => {
    acc[String.fromCodePoint(index)] = cur;
    return acc;
  }, {})
);
const animationOptions = ref({
  ease: (x) => {
    const n1 = 7.5625;
    const d1 = 2.75;

    if (x < 1 / d1) {
      return n1 * x * x;
    } else if (x < 2 / d1) {
      return n1 * (x -= 1.5 / d1) * x + 0.75;
    } else if (x < 2.5 / d1) {
      return n1 * (x -= 2.25 / d1) * x + 0.9375;
    } else {
      return n1 * (x -= 2.625 / d1) * x + 0.984375;
    }
  },
});
const keyframes = ({ value, direction }) => {
  const from = direction === "up" ? Math.max(...value) : Math.min(...value);
  const to = direction === "up" ? Math.min(...value) : Math.max(...value);
  return {
    transform: [
      `rotateX(-${from * (360 / 69)}deg)`,
      `rotateX(-${to * (360 / 69)}deg)`,
    ],
  };
};

const value = ref(0);

const partDataOptions = ref({
  sampleCount: rollerList.length,
  sampling: () => rollerList.map((_, index) => index),
});

function switchString() {
  value.value = Math.floor(Math.random() * 1000) % rollerList.length;
}

function cellStyle({ data }) {
  return data.map((partData) =>
    partData.digits.map((digit) =>
      digit.data.map((_, i, array) => ({
        position: "absolute",
        opacity: 1,
        top: 0,
        bottom: "auto",
        backgroundImage: `url(${MatchingSpinnerTiles})`,
        backgroundSize: "cover",
        backgroundPositionY:
          i % 3 === 0 ? "-1px" : i % 3 === 1 ? "-24px" : "-48px",
        // backgroundPositionY: '-1px',
        transform: `rotateX(${(i * 360) / array.length}deg) translateZ(264px)`,
      }))
    )
  );
}
onMounted(() => {
  document.fonts.add(
    new FontFace("GBJenLei", `url(${GBJenLei})`, {
      style: "normal",
      weight: "normal",
    })
  );
});
</script>

<template>
  <div class="hearthstone-demo relative text-center bg-black">
    <div class="overflow-hidden">
      <vue-to-counter-string
        class="font-bold pt-40 pb-48 mb-8 -mt-2"
        :style="{
          fontSize: '16px',
          lineHeight: '1',
          textShadow: `
            -0.5px -0.5px 0.5px #000,
            0.5px -0.5px 0.5px #000,
            -0.5px 0.5px 0.5px #000,
            0.5px 0.5px 0.5px #000
          `,
          fontFamily: 'GBJenLei',
        }"
        :value="String.fromCodePoint(value)"
        :alphabet="Object.keys(digitToChar).join('')"
        :digit-to-char="digitToChar"
        :min-places="[0, 0]"
        :duration="2"
        color="white"
        :part-data-options="partDataOptions"
        :animation-options="animationOptions"
        :keyframes="keyframes"
        :cell-style="cellStyle"
      />
    </div>
    <img
      class="absolute top-0 w-full h-full object-contain"
      :src="MatchMaker_phone"
      alt="bg"
    />
  </div>
  <hr />
  <div class="flex gap-4">
    <input class="border border-solid p-1" v-model="value" type="number" />
    <button class="border border-solid p-1" @click="switchString">切换</button>
    <span class="flex-auto" />
  </div>
  <hr />
</template>

<style lang="scss">
.hearthstone-demo {
  .vue-to-counter {
    @apply overflow-x-visible overflow-y-clip;

    .roller-part__wrapper {
      perspective: 1200px;

      & > span {
        display: block;
        transform-style: preserve-3d;
      }

      .roll-list {
        display: block !important;
        transform-style: preserve-3d;
        //height: 28px;
        transform: rotateX(0deg);

        .roll-item {
          display: block !important;
          padding: 4px 0;
        }
      }
    }
  }
}
</style>