鸽了6个月了,终于想起来这篇还没有写完…
年会预备了一个红包雨的活动,虽然到最后也没有用上,但是笔记还是要写的。
整个流程是关注公众号,然后回复 抢红包
返回一个授权地址获取用户授权信息,然后跳转到活动h5页面,抢红包的同时会在大荧幕上实时展示排名数据。
📱 先说说手机端部分

手机端的部分其实很简单,从url上边获取用户的 openID
,然后请求接口返回用户信息,这个时候也会返回活动是否开始的状态
- 活动并没有开始,那么就进入手动进入的页面,用户手动点击按钮进入
活动入口页
;
- 活动已经开始则直接进入
预开始页
;
- 缺失openID,提示授权失败返回授权页面重新获取授权。
参与用等待大荧幕指令,在大银幕倒数时就可以点击开始按钮进入同步的倒计时页面,同时会请求后端数据,这个时候后端会返回给我该用户被分配到的金额(哈,你没看错,后台已经按照设置的总金额给每个用户分配好了红包金额),然后按照分配到的金额数来判断是否会刷出 188
、88
等大红包,怕用户错过这个大红包还做了特殊的样式(金色红包),剩下的金额则按照设置的持续时间数来生成具体的红包雨,一般来说会在收到数据的同时生成完红包雨数据,倒计时结束之后就开始下落。
生成红包金额代码片段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| rainBuild() { let total = this.info.amount; let lucky = []; if (total > 18800) { total -= 18800; lucky.push({ amount: 18800 }); } if (total > 8800) { total -= 8800; lucky.push({ amount: 8800 }); } const timer = 10; let data = []; const length = Math.max( Math.ceil(((this.info.duration / 1000) * 4) / timer), 1 ); const amount = total / timer; for (let i = 0; i < timer; i++) { let temp = this.randomAlloc(amount, length, 0, amount); data.push(...temp); } if (lucky.length) { lucky.forEach(item => { let random = Math.floor(Math.random() * (data.length - 10)); data.splice(random, 0, item); }); } this.data = data; }
randomAlloc(total, length, min, max) { if (min * length > total || max * length < total) { throw Error(`没法满足最最少 ${min} 最大 ${max} 的条件`); } const result = []; let restValue = total; let restLength = length; for (let i = 1; i < length; i++) { restLength--; const restMin = restLength * min; const restMax = restLength * max; const usable = restValue - restMin; const minValue = Math.max(min, restValue - restMax); const limit = Math.min(usable - minValue, (max - minValue) * 2); const amount = Math.min( max, minValue + Math.floor(limit * Math.random()) ); result.push({ amount: amount }); restValue -= amount; } result[length - 1] = { amount: Math.floor(restValue) }; return result; },
|
下落的具体代码我从Github上找了一个开源的红包下落的构造函数,然后按照公司的需求改写了一下,直接贴上来吧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
| function luckyMoney(options) { this.el = options.el; this.rain = []; this.speed = options.speed; this.density = options.density; this.callback = options.callback; }
luckyMoney.prototype.create = function(id, amount) { const el = this.el, lucky = document.createElement("span"); let flag = true; lucky.setAttribute("amount", amount); lucky.setAttribute("title", (amount / 100).toFixed(2)); lucky.className = "luckyMoney"; if (amount > 1000) lucky.setAttribute("lucky", "lucky"); lucky.style.left = Math.random() * (el.clientWidth * 0.5) + el.clientWidth * 0.2 + "px"; lucky.style.top = -el.clientHeight / 10 + "px"; el.appendChild(lucky); this.rain.push(lucky); this.move(lucky); var handler = e => { if (flag === true) { e.target.className = "luckyMoney opened"; this.callback(e); flag = false; } else { return; } }; document.addEventListener("touchstart", function(e) { if (e.target.className === "luckyMoney") { handler(e); } else if (e.target.getAttribute("amount") === "0") { e.target.className = "luckyMoney luckyMoneyNone"; } else { return false; } }); };
luckyMoney.prototype.start = function(data) { let i = 0; this.timer = setInterval(() => { if (i < data.length) { const id = data[i].id, amount = data[i].amount; this.create(id, amount); i++; } }, this.density); };
luckyMoney.prototype.stop = function() { clearInterval(this.timer); this.rain.forEach(rain => { clearInterval(rain.timer); }); };
luckyMoney.prototype.move = function(rain) { const el = this.el; let diffY = Math.random() / 2 + 0.4, diffX = Math.random() / 2; const amount = rain.getAttribute("amount"); if (amount > 1000) { diffY = 0.4; diffX = Math.random() / 10; } rain.timer = setInterval(() => { if (diffY > 1.5) { rain.style.left = parseInt(rain.style.left) + parseInt((diffX * rain.clientHeight) / 30) + "px"; } else { rain.style.left = parseInt(rain.style.left) - parseInt((diffX * rain.clientHeight) / 30) + "px"; } rain.style.top = parseInt(rain.style.top) + parseInt((diffY * rain.clientHeight) / 20) + "px"; const position = { top: parseInt(rain.style.top), left: parseInt(rain.style.left) }; if ( position.top > el.clientHeight || position.left > el.clientWidth || position.left < -100 ) { clearInterval(rain.timer); el.removeChild(rain); } }, this.speed); };
luckyMoney.prototype.clear = function() { const el = this.el, redItem = el.childNodes; for (let i = redItem.length - 1; i > -1; i--) { el.removeChild(redItem[i]); } }; export default luckyMoney;
|
每次点开红包之后会收集金额到 tempMoney
和 totalMoney
中,并且提交到后台然后清空 tempMoney
(这边做了1秒的节流操作),这样大银幕就能展示当前轮次的 Top5
用户。
手机端的大部分内容就是这样了,主要是下落这块比较麻烦,其它的都是一些样式的问题,稍微调试一下就行了。
🏮大荧幕部分

大银幕这块的话就容易很多了,本来是考虑用 WebSocket
来实时传输红包排名的,但是发现还不如轮询简单,所以还是我这边做了1秒间隔的轮询。
获取到数据之后跟新排名数据,界面就会实时刷新了,这边在展示用户排名时做了动画,每次用户新进和排名更替都会进行左右移动,并不只是简单的修改了展示的数据。
用到了transform
的偏移量来修改展示的位置,这样就可以通过修改元素次序来打到用户排名更替补间动画了
Stylus片段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| .rank-box width 90% height 300px max-width 1600px display flex justify-content space-around position absolute left 50% bottom 5vh z-index 5 transform translateX(-50%) .lucky-box width 180px height 260px line-height 25px font-size 18px background url('~assets/img/luckyBox.png') center no-repeat background-size 100% 100% padding 20px border-radius 15px border #d5b06e 3px solid box-shadow 10px 10px 32px rgba(black, 0.45) display flex justify-content center align-items center flex-direction column position absolute left 50% bottom 0 transform translateX(800px) opacity 1 transition all 0.5s box-sizing border-box &:nth-child(1) transform translateX(-350%) z-index 10 &:nth-child(2) transform translateX(-200%) z-index 9 &:nth-child(3) transform translateX(-50%) z-index 8 &:nth-child(4) transform translateX(100%) z-index 7 &:nth-child(5) transform translateX(250%) z-index 6 &:nth-child(5)~.lucky-box transform translateX(800px) z-index 5 opacity 0 .avatar width (@width / 2) height @width border #d5b06e 4px solid border-radius 50% margin 0 auto 10px display block
|