admin管理员组文章数量:1794759
让CSS3中Transform属性带你一文实现炫酷的转盘抽奖效果
前端时间有个需求是客户端双端APP内嵌入整个转盘抽奖的web子系统,具体是要在后台能够控制大转盘抽奖的奖项数,和用户免费抽奖的次数,并且免费抽奖使用完,用户可以观看广告进行抽奖或使用积分抽奖。正好最近有空,出了这篇教程,解析转盘抽奖的实现过程。
此子系统整体开发的话由我负责,其中前端技术:H5+CSS3+JS;后端技术:YII2+Redis。
转盘演示视频效果如下所示:
让CSS3中Transform属性带你一文实现炫酷的转盘抽奖效果
下面分两个部分介绍下这个转盘抽奖部分:
一、前端样式及抽奖操作开发
关于前端展示主要使用Transform来实现,下面是关于Transform属性【大家可以去菜鸟教程等看下其详细用法,感觉这个属性效果很炫酷】的介绍,其中主要用到的是rotate(定义 2D 旋转,在参数中规定角度)、skewX(定义沿着 X 轴的 2D 倾斜转换)
以我们代码为例:在prize-list内我们先做出整个转盘的背景图,prize-reward内展示每一个奖品信。在首次进入页面时,调用接口获取转盘奖品信,如:名称、icon图、奖品ID等,然后循环组装div标签,组装完成后,根据返回的奖品信计算奖品条数,然后设置Transform属性的rotate和skewX,因为背景颜色是间隔展示,所以展示的奖品数为偶数,最多展示的奖项最大值为12。当时做的时候没有找到每次赋值的规律,所以2-12的情景值,是我尝试出来的,到这里的话整个的样式其实就已经出来了,视频展示的为6个奖品项效果,接口返回的奖品条数不同,此页面展示不同效果。
此部分就是转盘部分的布局:
<div class="wheelSurf"> <div class="wheel"> <div class="wheel-icon"> <img src="../images/wheel/wheel_back.png" alt=""> </div> <div class="prize-box"> <div class="prize-content"> <!-- 转盘的背景盒子,JS填充奖品背景 --> <div class="prize-list" id="prize-list"></div> <!-- 转盘的奖品盒子,JS填充奖品 --> <div class="prize-reward" id="prize-reward"></div> </div> <!-- 点击抽奖按钮 --> <div class="prize-button" id="prize-button"> <img src="../images/wheel/start_button.png" alt=""> </div> </div> <div class="score"> <div class="spend_score"> <span class="consume-score">0</span>积分/次 </div> <div class="my_score"> 我的积分:<span id="my-score">0</span> </div> </div> </div> <div class="activity_rule"> <div class="rule_title">活动规则</div> <div class="rule_list" id="activity-rule"></div> </div> </div>此部分就是转盘奖品展示的代码:
/** * 大转盘填充奖项,初始化展示转盘背景 * @param data */ function prize(data) { let list = ''; let reward = ''; let count = data.length; let rotate = 360 / count; prizeCount = count; prizeDeg = 360 / count; let itemRotate = 360 / 2 / count; let liRotate = 0; // 设置4、6、8、10、12数量奖品的旋转角度值 let width; switch (count) { case 2: liRotate = 0; width = 4; break; case 4: liRotate = 0; width = 3; break; case 6: liRotate = 30; width = 2; break; case 8: liRotate = 45; width = 1.5; break; case 10: liRotate = 54; width = 1.3; break; case 12: liRotate = 60; width = 1; break; default: liRotate = 0; width = 1; } // 组装背景和奖品的标签,且设置奖品的旋转角度 for(let i=0; i < count; i++) { list += "<div class=\\"prize-li\\"></div>"; reward += "<div class=\\"prize-item\\" data-id=\\"" + data[i]['id'] + "\\" style=\\"transform: rotate(" + (rotate*(i+1) - itemRotate) + "deg) translateX(-50%);width: " + width + "rem;\\">\\n" + " <div class=\\"prize-name\\">\\n" + data[i]['title'] + " </div>\\n" + " <div class=\\"prize-icon\\">\\n" + " <img src=\\"" + data[i]['icon'] + "\\">\\n" + " </div>\\n" + " </div>" } // 填充内容 $('#prize-list').html(list); $('#prize-reward').html(reward); // 获取所以的背景标签,循环设置背景颜色及旋转的角度值和倾斜转换值 [].slice.call(document.querySelectorAll('.prize-li'), 0).forEach(function (item, i) { if(count == 2) { return false; } item.style.backgroundColor = '#ffffff'; if(i%2 == 0) { item.style.backgroundColor = '#FCE9C1'; } item.style.transform = 'rotate(' + (360 / count * i + liRotate) + 'deg) skewX(' + liRotate +'deg)'; }); // 如果只有两个奖品则单独处理下样式 if(count == 2) { $('#prize-list').find('.prize-li').css('width', '2.49rem'); $('#prize-list').find('.prize-li').css('height', '4.98rem'); $('#prize-list').find('.prize-li').css('top', '0rem'); $('#prize-list').find('.prize-li').css('left', '0rem'); $('#prize-list').find('.prize-li').eq(0).css('background', '#FCE9C1'); $('#prize-list').find('.prize-li').eq(1).css('left', '2.49rem'); } }接下来就是前端抽奖操作的实现,用户点击抽奖按钮进行抽奖,首先的话前端设置一个抽奖锁,在用点击抽奖按钮的时候将锁锁上,在这次抽奖过程完成后,将锁打开,用户点击抽奖,请求抽奖接口,然后接口进行抽奖逻辑处理,将最终的奖品信返回给前端,也就是奖品的ID,然后前端根据ID拿到奖品的标签下标,前端使用transition方式渲染装盘,最终将指针停在转盘内接口返回的奖品那里,用户看到中奖信进行下步操作。这样的话,整个前端抽奖过程就完成了
此部分是转盘抽奖部分代码
/** * 抽奖操作 * @param type */ function lottery(type) { // 请求后端接口,获取中奖信 $.ajax({ url: wheel_lottery, type: 'get', headers: { "token": token, "Accept": "application/json", "appid": appId }, data: { type: type // 用户抽奖类型 }, success:function (res) { if (!res.data) { flag = true; return false; } var type = res.data.type; var title = res.data.title; var img = res.data.img; var rid = res.data.record_id; myScore = res.data.my_score; pid = res.data.prize_id; // 更新积分及抽奖次数等信 count--; $('#prize-count').html(count); // 获取当前奖项标签的下标,用于计算转盘转动的角度值 var code = $("#prize-reward").find(".prize-item[data-id=" + pid + "]").index(); if(code == -1) { // 未查询到标签 flag = true; return false; } // 转盘转动,设置转盘转动的角度值 var e = 3600 - (code * prizeDeg) - prizeDeg / 2; $('.prize-content').css({'transition': 'transform 6s cubic-bezier(0.25, 0.1, 0.01, 1)', 'transform': "rotate(" + e + "deg)"}); setTimeout(function () { // 消提示 $('#mask').css('display', 'block'); $('#my-score').html(myScore); if (type == 1) { $('#prize-score').css('display', 'block'); $('#result-img').attr('src', img); $('#result-score').html(title); } else if(type == 2) { $('#prize-result').css('display', 'block'); $('#result-icon').attr('src', img); $('#result-reward').html(title); $('#go-write').attr('data-id', rid) } else { $('#prize-no').css('display', 'block'); } // 转盘复位 $('.prize-content').css({'transition': '','transform': "rotate(0deg)"}); // 打开抽奖锁 flag = true; }, 6000); }, error:function(res) { } }) }二、后端接口抽奖逻辑开发
其实关于设置展示的奖项条数、获取奖项数据这些接口没啥要讲的,就是一般的增删改查操作,其中有一点需要特别强调下,一般的抽奖系统是能够控制每个奖项的中奖概率的,这里我们通过设置weight【权重】字段值来控制中奖概率,weight值越大中奖几率越高。
确定中奖奖品代码:
/** * 抽奖方法,返回中奖商品id * * @return mixed */ private function _getLotteryPrize() { // 所有奖品的权重和id $prize = WheelAwardPrize::select('id', 'weight') ->orderBy('created_at', 'desc') ->limit(Tenancy::setting('wheel.prize_num')) ->get(); $data = []; // 最终抽奖奖品数组 $totalWeight = 0; // 初始化总权重 foreach ($prize as $item) { $weight = $item['weight']; // 当前奖品权重值 if (!$weight) { // 没有权重值跳过 continue; } for ($i = 1; $i <= $weight; $i++) { // 循环添加进最终数组 $data[] = $item; } $totalWeight += $weight; // 增加总权重 } // 随机获取中奖奖品下标 $index = rand(0, $totalWeight - 1); // 返回奖品ID return $data[$index]['id']; }下面就主要讲一下抽奖逻辑的实现:首先和前端一样,设置抽奖锁,完成抽奖记得释放锁,同一个用户完成一次抽奖流程后,才能允许进行下一次抽奖。然后确定用户中奖奖品,从库中查询全部奖品ID【奖品主键值】和weight【权重】,然后循环weight,将当前weight的ID和weight放置在一个二维数组内,这样我们就能拿到一个不存放weight为0,且长度为weight总值的奖品数组,然后根据数组长度随机取一个范围内的值,这样我们就拿到当前index的值,进而拿到中奖奖品的ID。最后的话,就是根据中奖奖品ID,生成用户的中奖信,如果是虚拟货币的话,就直接给用户充值,实物奖励的话,前端就提示用户填写收货信,在把奖品ID和用户中奖记录的ID通过接口返回给前端。
抽奖流程代码:
/** * 进行抽奖,返回奖品ID * * @return array */ public function lottery(Request $request) { // 加锁 $lockKey = RedisKey::getApiLockKey('wheel/lottery', ['member_id' => auth()->user()->member_id]); if (Cache::has($lockKey)) { // 有锁 throw new BusinessException('您的操作太过频繁,请稍后重试'); } // 抽奖操作,获取中奖奖项ID $pid = $this->_getLotteryPrize(); if (!$pid) { throw new BusinessException('奖品正在准备中,请稍后重试'); } // 添加奖励记录 $records = new WheelAwardRecord(); $records->member_id = auth()->user()->member_id; $records->wheel_id = $pid; $records->type = $request->type; $records->date = date('Ymd'); $asset = Asset::where('member_id', auth()->user()->member_id)->first(); if ($request->type == 2) { // 积分抽奖 $config = Tenancy::setting('wheel'); if (!$asset || ($asset->score < $config['consume_score'])) { Cache::forget($lockKey); throw new BusinessException('积分不足'); } // 扣除用户积分 $this->user->changeScore(-$config['consume_score'], ScoreReason::REASON_WHEEL, Task::TASK_WHEEL); } // 发放奖励 $prizes = WheelAwardPrize::where('id', $pid)->first(); if ($prizes->type == 1) { // 奖励虚拟货币 $records->status = 3; // 增加用户积分 $this->user->changeScore($config['consume_score'], ScoreReason::REASON_WHEEL, Task::TASK_WHEEL); } else if($prizes->type == 2) { // 实物奖励 $records->status = 1; // 默认状态待发放 } else { // 未中奖 $records->status = 3; // 默认状态待发放 } $records->save(); // 移除锁 Cache::forget($lockKey); return [ 'prize_id' => $pid, 'type' => $prizes->type, 'title' => $prizes->title, 'img' => $prizes->icon, 'record_id' => $records->id, 'my_score' => 600 ]; }不知不觉来北京已经一年半,明天就要结束北漂的生活,准备回老家休一段时间,然后10号左右去上海找工作。
因为我现在做的是小说阅读方面的开发,所以趁着休的这段时间出一篇小说阅读器的博文,大概是《一文带你掌握H5小说阅读器的开发,仿起点读书阅读器》,解析小说阅读器的开发过程,这个开发的话是准备仿照起点阅读阅读器开发,从0-1搭建小说阅读器。
版权声明:本文标题:让CSS3中Transform属性带你一文实现炫酷的转盘抽奖效果 内容由林淑君副主任自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.xiehuijuan.com/baike/1686971316a123911.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论