admin管理员组

文章数量:1794759

让CSS3中Transform属性带你一文实现炫酷的转盘抽奖效果

让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搭建小说阅读器。

本文标签: 转盘带你一文属性效果