vue 前端红包雨功能

效果预览

纯前端红包雨功能

使用了vant,postcss-px-to-viewport,lodash

项目结构

 reset.css
 

html,
body {
  height: 100%;
  /* 文字风格 Sans-serif 各笔画粗细相同,Serif 笔画粗细不同,monospace 等宽体,cursive草书,fantasy梦幻 */
  font-family: 'Microsoft YaHei', sans-serif, 'Helvetica Neue', Helvetica, Arial,
    '黑体', '宋体', Arial;
  -webkit-tap-highlight-color: transparent;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

body {
  font-size: 14px;
  color: #333;
}

/* 重置各标签的默认样式 */
a,
body,
center,
cite,
code,
dd,
del,
div,
dl,
dt,
em,
fieldset,
figcaption,
figure,
footer,
form,
h1,
h2,
h3,
h4,
h5,
h6,
header,
hr,
html,
img,
input,
label,
legend,
li,
mark,
ol,
p,
section,
span,
textarea,
time,
td,
th,
ul {
  margin: 0;
  border: 0;
  padding: 0;
  font-style: normal;
  box-sizing: border-box;
  /*  自动换行 */
  word-wrap: break-word;
  /*  强制英文单词断行 */
  word-break: break-all;
}

/*  设置标签为块级分类 */
article,
aside,
details,
fieldset,
figcaption,
figure,
footer,
header,
main,
nav,
section {
  display: block;
}

/* 去除input标签的默认样式 */
button,
input,
textarea {
  -webkit-appearance: none;
  font-family: 'Microsoft YaHei', sans-serif, 'Helvetica Neue', Helvetica, Arial,
    '黑体', '宋体', Arial;
  border: 0;
  margin: 0;
  padding: 0;
  font-size: 1em;
  line-height: 1em;
  outline: none;
  background-color: transparent;
}

/*  禁止多文本框手动拖动大小 */
textarea {
  resize: none;
  -webkit-appearance: none;
}

/* 去掉按下的阴影盒子 */
input,
textarea,
a {
  -webkit-tap-highlight-color: transparent;
}

/*  清除a标签下划线 */
a,
a:visited {
  text-decoration: none;
}

a:focus,
a:active,
a:hover {
  outline: none;
}

/*  清除列表前面的点 */
ol,
li,
ul {
  list-style: none;
}

/*  清除IE下图片的边框 */
img {
  border-style: none;
  font-size: 0;
}

/*  解决chrome浏览器默认黄色背景问题 */
input:-webkit-autofill,
textarea:-webkit-autofill,
select:-webkit-autofill {
  -webkit-box-shadow: 0 0 0 1000px #fff inset;
}

/*  设置默认滚动条样式 */
::-webkit-input-placeholder {
  color: #afbdcc;
}

:-moz-placeholder {
  color: #afbdcc;
}

::-moz-placeholder {
  color: #afbdcc;
}

:-ms-input-placeholder {
  color: #afbdcc;
}

::-webkit-scrollbar {
  width: 6px;
  height: 6px;
}

::-webkit-scrollbar-track {
  background-color: #f5f5f5;
}

::-webkit-scrollbar-track-piece {
  background-color: #f5f5f5;
  border-radius: 6px;
}

::-webkit-scrollbar-thumb {
  background-color: #cccccc;
  border-radius: 6px;
}

::-webkit-scrollbar-corner {
  background-color: #f5f5f5;
}

::-webkit-resizer {
  background-repeat: no-repeat;
  background-position: bottom right;
}

RedPacket.js

// @ts-ignore
import RedPacketImgUrl from "../assets/image/RedPacket.png";
// @ts-ignore
import giftImgUrl from "../assets/image/liwu.png";
import { sample, random } from "lodash";
export default class RedPacket {
  constructor(options) {
    //类型 奇数:红包 偶数:礼物
    this.type = options.type || 1;
    //红包图片地址
    this.url = options.url || RedPacketImgUrl;
    // 礼物图片地址
    this.giftUrl = options.giftUrl || giftImgUrl;
    //红包宽度
    this.width = options.width || "20vw";
    //红包高度
    this.height = options.height || "auto";
    //红包的轨道
    this.track = options.track || sample([0, 10, 20, 30, 40, 50, 60, 70, 80]);
    //红包旋转角度
    this.angle = options.angle || random(-360, 360);
    // 红包掉落速度
    this.speed = options.speed || random(3.1, 6.1);
    //红包回调,
    this.callback = options.callback;
    //红包放入那个容器
    this.parent = options.parent || document.body;
    //创建红包
    this.create();
  }
  //创建方法
  create() {
    //创建一个红包dom元素
    const img = document.createElement("img");
    //图片地址
    if (this.type % 2 === 0) {
      //偶数:礼物
      img.src = this.giftUrl;
    } else {
      //奇数:红包
      img.src = this.url;
    }
    //图片宽度
    img.style.width = this.width;
    //图片高度
    img.style.height = this.height;
    //开启定位
    img.style.position = "absolute";
    //红包轨道
    img.style.left = this.track + "vw";
    img.animate(
      [
        { transform: "translateY(-20vh) rotate(0)" },
        { transform: `translateY(110vh) rotate(${this.angle}deg)` },
      ],
      {
        duration: this.speed * 1000,
        fill: "forwards",
        easing: "cubic-bezier(0.42, 0, 0.58, 1)", // 使用贝塞尔函数
      }
    );
    // 图片的回调
    img.ontouchstart = this.destroy.bind(this, img);
    //放入容器
    this.parent.appendChild(img);
    setTimeout(() => {
      // @ts-ignore
      img.remove();
    }, this.speed * 1000);
  }
  //图片销毁方法
  destroy(currentImg) {
    // @ts-ignore
    currentImg.remove();
    if (this.type % 2 === 0) {
      //偶数:礼物
      this.callback(2);
    } else {
      //奇数:红包
      this.callback(1);
    }
  }
}

finally.vue

<template>
  <van-overlay :show="isShow">
    <div class="wrapper">
      <div class="boxs">
        <div class="one">最终奖励:</div>
        <div class="two">抢到红包个数: {{ redPacketNumber }}个</div>
        <div class="three">抢到礼物个数: {{ giftNumber }}个</div>
      </div>
    </div>
  </van-overlay>
</template>

<script>
  export default {
    name: "finally",
    data() {
      return {
        isShow: false,
        redPacketNumber: 0, //红包个数
        giftNumber: 0, //礼物个数
      };
    },
    methods: {
      show(val1, val2) {
        this.redPacketNumber = val1;
        this.giftNumber = val2;
        this.isShow = true;
      },
    },
  };
</script>

<style scoped lang="scss">
  .wrapper {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
  }
  .boxs {
    text-align: center;
    color: orange;
    font-family: serif;
    font-size: 26px;
    .one {
      font-size: 32px;
      margin-bottom: 20px;
      color: #fe3431;
    }
    .two {
      margin-bottom: 10px;
    }
  }
</style>

meng.vue

<template>
  <div>
    <van-overlay :show="isShow">
      <div class="wrapper">
        <div>
          <div class="title">红包雨即将开始</div>
          <van-count-down
            ref="countDown"
            :time="times"
            :auto-start="false"
            @finish="finishs"
          >
            <template #default="timeData">
              <h1 class="block">{{ timeData.seconds }}</h1>
            </template>
          </van-count-down>
        </div>
      </div>
    </van-overlay>
  </div>
</template>

<script>
  export default {
    name: "meng",
    props: ["onFinish"],
    data() {
      return {
        isShow: false,
        times: 3500,
      };
    },
    methods: {
      show(time = 3500) {
        this.isShow = true;
        this.times = time;
        this.$refs.countDown.start();
      },
      finishs() {
        this.isShow = false;
        this.onFinish(111);
      },
    },
  };
</script>

<style scoped lang="scss">
  .wrapper {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
  }
  .block {
    font-size: 60px;
    font-weight: normal;
    font-family: serif;
    color: orange;
    text-align: center;
  }
  .title {
    position: fixed;
    width: 100%;
    top: 20%;
    left: 0;
    font-size: 30px;
    font-weight: normal;
    font-family: serif;
    color: #fe3431;
    text-align: center;
  }
</style>

App.vue

<template>
  <div id="app">
    <meng ref="meng" :onFinish="onFinishs"></meng>
    <jiangli ref="jiangli"></jiangli>

    <div class="box" ref="RedPacketBox"></div>
    <div v-if="boxShow" class="boxs">
      <div>
        <div class="imgs" @click="openCountdown">
          <img src="./assets/image/RedPacket.png" alt="" />
        </div>
      </div>
    </div>
  </div>
</template>

<script>
  import meng from "./components/meng.vue";
  import jiangli from "./components/finally.vue";
  import RedPacket from "./class/RedPacket";
  import { random } from "lodash";
  export default {
    name: "App",
    components: {
      meng,
      jiangli,
    },
    data() {
      return {
        boxShow: true,
        timer: null,
        redPacketNumber: 0, //红包个数
        giftNumber: 0, //礼物个数
      };
    },
    mounted() {},
    methods: {
      //开启倒计时
      openCountdown() {
        this.boxShow = false;
        this.$refs.meng.show(3500);
      },
      //倒计时结束
      onFinishs() {
        this.setRedPacket(200, 20000);
      },
      //红包雨结束
      redPacketEnd() {
        this.$refs.jiangli.show(this.redPacketNumber, this.giftNumber);
      },
      //点击红包的回调
      clickRedPacket(val) {
        if (val % 2 === 0) {
          //偶数:礼物
          this.giftNumber++;
        } else {
          //奇数:红包
          this.redPacketNumber++;
        }
      },
      setRedPacket(speed, time) {
        const that = this;
        //下雨
        this.timer = setInterval(() => {
          new RedPacket({
            parent: this.$refs.RedPacketBox,
            type: random(0, 10),
            callback: this.clickRedPacket,
          });
        }, speed);
        //停止
        setTimeout(() => {
          clearInterval(this.timer);
          that.redPacketEnd();
        }, time);
      },
    },
  };
</script>

<style lang="scss" scoped>
  .box {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-image: linear-gradient(180deg, rgb(230, 190, 117), orange);
    overflow: hidden;
  }
  .boxs {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-image: linear-gradient(180deg, rgb(230, 190, 117), orange);
    overflow: hidden;
    display: flex;
    justify-content: center;
    align-items: center;
    .imgs {
      width: 100px;
      margin: 0 auto;
      margin-bottom: 10px;
      animation: shake 0.5s infinite;
      img {
        width: 100%;
        height: auto;
      }
    }
  }
  @keyframes shake {
    0% {
      transform: rotate(0);
    }
    25% {
      transform: rotate(-10deg);
    }
    50% {
      transform: rotate(10deg);
    }
    75% {
      transform: rotate(-10deg);
    }
    100% {
      transform: rotate(0);
    }
  }
</style>

素材图(可以私信我要)

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值