【小程序】微信小程序九宫格抽奖动画(完整版)

这是一个微信小程序九宫格抽奖页面的完整代码,包括 WXML、WXSS、JS 和 JSON。

效果

九宫格抽奖

功能说明:

  1. 静态页面布局: 3x3 九宫格,中间是"立即抽奖"按钮,周围是奖品金额。
  2. 抽奖动画: 点击"立即抽奖"后,九宫格会动态跑马灯式高亮显示,最终停留在中奖项。
  3. 中奖提示: 抽奖结束后弹出中奖结果。

1. lottery.json (页面配置)

json 复制代码
{
  "navigationBarTitleText": "九宫格抽奖",
  "usingComponents": {}
}

2. lottery.wxml (页面结构)

xml 复制代码
<view class="container">
  <view class="lottery-grid">
    <block wx:for="{{gridItems}}" wx:key="id">
      <view
        class="lottery-item {{item.isButton ? 'lottery-btn' : ''}} {{item.active ? 'active' : ''}}"
        data-index="{{item.id}}"
        bindtap="{{item.isButton ? 'startLottery' : ''}}"
      >
        <text>{{item.text}}</text>
      </view>
    </block>
  </view>
</view>

3. lottery.wxss (页面样式)

css 复制代码
/* container */
.container {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh; /* 撑满整个屏幕高度 */
  background-color: #f8f8f8;
  padding: 20rpx;
  box-sizing: border-box;
}

/* 九宫格容器 */
.lottery-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr); /* 3列,每列等宽 */
  gap: 15rpx; /* 格子之间的间距 */
  width: 700rpx; /* 九宫格总宽度 */
  background-color: #fff;
  border-radius: 20rpx;
  padding: 15rpx;
  box-shadow: 0 5rpx 20rpx rgba(0, 0, 0, 0.1);
}

/* 抽奖格子公共样式 */
.lottery-item {
  width: 100%; /* 充满父容器的宽度 */
  height: 200rpx; /* 高度 */
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: #fdfdfd;
  border: 4rpx solid #ffd700; /* 金色边框 */
  border-radius: 15rpx;
  font-size: 36rpx;
  font-weight: bold;
  color: #333;
  transition: all 0.1s ease; /* 动画过渡效果 */
  box-sizing: border-box; /* 边框和内边距不撑大元素 */
}

/* 中间抽奖按钮样式 */
.lottery-btn {
  background-color: #ffda47; /* 醒目的黄色 */
  color: #d84e27; /* 红色字体 */
  font-size: 40rpx;
  border-color: #f77026; /* 橙色边框 */
  box-shadow: 0 4rpx 8rpx rgba(255, 165, 0, 0.4);
  cursor: pointer;
}

/* 激活(高亮)状态样式 */
.lottery-item.active {
  background-color: #ff4d4f; /* 红色高亮 */
  color: #fff;
  border-color: #ff0000;
  transform: scale(1.03); /* 稍微放大 */
  box-shadow: 0 0 20rpx rgba(255, 77, 79, 0.8);
}

/* 按钮禁用状态(抽奖进行中) */
.lottery-btn[disabled] {
  opacity: 0.7;
  cursor: not-allowed;
  background-color: #ccc;
  border-color: #aaa;
}

4. lottery.js (页面逻辑)

javascript 复制代码
Page({
  data: {
    // 九宫格数据
    // id: 格子索引 (0-8)
    // text: 显示的文本
    // isButton: 是否是抽奖按钮 (中间格子)
    // active: 是否是当前高亮格子
    gridItems: [],

    // 抽奖相关状态
    currentIndex: -1,     // 当前高亮的格子索引
    isDrawing: false,     // 是否正在抽奖
    timer: null,          // 抽奖定时器
    speed: 200,           // 初始转动速度 (ms)
    minSpeed: 50,         // 最快转动速度 (ms)
    drawCount: 0,         // 已经转动的次数
    totalRounds: 3,       // 至少转动多少圈 (影响动画时长)
    finalPrizeIndex: -1,  // 最终中奖的格子索引
    accelerateSteps: 10,  // 加速阶段的步数
    decelerateSteps: 10,  // 减速阶段的步数
    
    // 抽奖路径 (按顺时针方向,跳过中间按钮4)
    // 0 1 2
    // 7 X 3  (X是中间按钮4)
    // 6 5 4
    lotteryPath: [0, 1, 2, 5, 8, 7, 6, 3],
  },

  onLoad: function () {
    this.initGridItems();
  },

  /**
   * 初始化九宫格数据
   */
  initGridItems: function () {
    const initialGridData = [
      { id: 0, text: '5元' },
      { id: 1, text: '8元' },
      { id: 2, text: '10元' },
      { id: 3, text: '90元' }, // 注意这个位置,对应抽奖路径
      { id: 4, text: '立即抽奖', isButton: true }, // 中间按钮
      { id: 5, text: '20元' }, // 注意这个位置,对应抽奖路径
      { id: 6, text: '50元' },
      { id: 7, text: '40元' },
      { id: 8, text: '30元' }
    ];

    const gridItems = initialGridData.map(item => ({
      ...item,
      active: false,
      isButton: item.isButton || false // 确保 isButton 属性存在
    }));

    this.setData({
      gridItems: gridItems
    });
  },

  /**
   * 获取下一个高亮的格子索引
   */
  getNextLotteryIndex: function (currentPathIndex) {
    const { lotteryPath } = this.data;
    return (currentPathIndex + 1) % lotteryPath.length;
  },

  /**
   * 开始抽奖
   */
  startLottery: function () {
    if (this.data.isDrawing) {
      wx.showToast({
        title: '正在抽奖中...',
        icon: 'none'
      });
      return;
    }

    this.setData({
      isDrawing: true,
      drawCount: 0,
      speed: 200, // 恢复初始速度
    });
    
    // 清除上次的定时器,避免重复
    if (this.data.timer) {
      clearTimeout(this.data.timer);
    }

    // 模拟抽奖结果,这里排除中间按钮(index 4)
    const prizePoolIndexes = this.data.lotteryPath; // 只有周围的8个格子能中奖
    const randomIndexInPool = Math.floor(Math.random() * prizePoolIndexes.length);
    const finalPrizeIndex = prizePoolIndexes[randomIndexInPool];

    console.log("最终中奖格子索引 (在 lotteryPath 中的位置):", randomIndexInPool);
    console.log("最终中奖格子在 gridItems 中的实际ID:", finalPrizeIndex);

    this.setData({
      finalPrizeIndex: finalPrizeIndex,
      currentIndex: this.data.lotteryPath[0] // 初始从第一个格子开始
    }, () => {
      // 确保 finalPrizeIndex 设置后再启动动画
      this.runLottery();
    });
  },

  /**
   * 运行抽奖动画
   */
  runLottery: function () {
    let { gridItems, currentIndex, drawCount, speed, minSpeed,
          finalPrizeIndex, lotteryPath, totalRounds, accelerateSteps, decelerateSteps } = this.data;

    // 清除上一个高亮
    if (currentIndex !== -1) {
      let oldGridItems = gridItems;
      const oldIndexInPath = lotteryPath.indexOf(currentIndex);
      oldGridItems[lotteryPath[oldIndexInPath]].active = false;
      this.setData({ gridItems: oldGridItems });
    }

    // 计算下一个高亮索引
    const currentPathIndex = lotteryPath.indexOf(currentIndex);
    const nextPathIndex = this.getNextLotteryIndex(currentPathIndex);
    const nextActualIndex = lotteryPath[nextPathIndex];

    // 更新高亮
    gridItems[nextActualIndex].active = true;
    this.setData({
      gridItems: gridItems,
      currentIndex: nextActualIndex,
      drawCount: drawCount + 1
    });

    // 计算总的转动步数,确保至少转totalRounds圈 + 停到中奖位置
    // 假设 finalPrizeIndex 是在 lotteryPath 中的实际索引
    const prizePathIndex = lotteryPath.indexOf(finalPrizeIndex);
    const minRunSteps = lotteryPath.length * totalRounds + prizePathIndex + 1; // 至少转的步数

    // 速度控制
    let newSpeed = speed;
    if (drawCount < accelerateSteps) { // 加速阶段
      newSpeed = Math.max(minSpeed, speed - (speed - minSpeed) / accelerateSteps * drawCount);
    } else if (drawCount >= minRunSteps - decelerateSteps && drawCount < minRunSteps) { // 减速阶段
      newSpeed = Math.min(200, speed + (200 - minSpeed) / decelerateSteps);
    } else if (drawCount >= minRunSteps) { // 减速到最终结果,确保最终速度不会太快
      newSpeed = Math.min(newSpeed + 30, 400); // 逐渐变慢
    }

    // 检查是否停止
    const isStop = drawCount >= minRunSteps && currentIndex === finalPrizeIndex;

    if (isStop) {
      clearTimeout(this.data.timer);
      this.setData({
        isDrawing: false,
        timer: null
      });

      wx.showModal({
        title: '恭喜!',
        content: '您抽中了' + gridItems[finalPrizeIndex].text + '!',
        showCancel: false,
        confirmText: '确定'
      });
    } else {
      this.setData({ speed: newSpeed });
      this.data.timer = setTimeout(() => this.runLottery(), newSpeed);
    }
  },

  onUnload: function() {
    // 页面卸载时清除定时器,避免内存泄漏
    if (this.data.timer) {
      clearTimeout(this.data.timer);
      this.setData({ timer: null });
    }
  }
});
相关推荐
小谭鸡米花2 小时前
uni小程序中使用Echarts图表
前端·小程序·echarts
2501_915909065 小时前
HTTPS 错误解析,常见 HTTPS 抓包失败、443 端口错误与 iOS 抓包调试全攻略
android·网络协议·ios·小程序·https·uni-app·iphone
源码师傅9 小时前
uniapp开源多商户小程序商城平台源码 支持二次开发+永久免费升级
小程序·uni-app·多商户商城源码·uniapp开源商城源码·开源多商户小程序商城平台·商城小程序代码·多商户商城小程序源码
杨天天.12 小时前
小程序原生实现音频播放器,下一首上一首切换,拖动进度条等功能
前端·javascript·小程序·音视频
weixin_1772972206912 小时前
盲盒抽卡机小程序:从0到1的蜕变之路
小程序·盲盒·抽谷机
说私域12 小时前
基于开源AI智能名片链动2+1模式S2B2C商城小程序的移动互联网人气氛围营造机制研究
人工智能·小程序·开源
流***陌17 小时前
用工招聘小程序:功能版块与前端设计解析
前端·小程序
Goona_17 小时前
PyQt数字转大写金额GUI工具开发及财务规范实现
python·小程序·交互·pyqt
文心快码BaiduComate18 小时前
我用Comate搭建「公园找搭子」神器,再也不孤单啦~
前端·后端·微信小程序