首页 > 建站教程 > APP开发,混合APP >  react-native结合webview完美实现抽奖效果正文

react-native结合webview完美实现抽奖效果

我爱模板网在做react-native app时,需要实现抽奖效果,正好前段时间,找了个不错的抽奖代码:GB Canvas Turntable —— 基于canvas的转盘抽奖js插件。因为这个抽奖插件是HTML5+CANVAS的,就想着结合webview来实现。当然,直接使用不行,找到了这个插件的未压缩版,修改了源码,再结合下面的步骤,终于实现了:



实现步骤:
1、新建choujiang.js,用来放抽奖代码,代码就是未压缩版GB Canvas Turntable的代码(进行了改动,将抽奖改成了异步,必须从后台获取抽奖结果后,才会停止转动),只不过将代码编程字符串,同时将调用抽奖的代码改成了函数式,在native页面,用injectedJavaScript来调用,传入goodsArr, cw, ch, headers,即奖品列表、宽度、高度和请求头,并且引入了ajax,在抽奖按钮点击时,转盘转动的过程中,去后台获取本地中奖产品的ID,然后传给回调,转盘转动结束,就会停留在后台指定的中奖区域。在中奖回调中,用
1window.ReactNativeWebView.postMessage(data)
的方法向native页面发送消息:
001import config from '../../utils/config';
002const html = `
003<!DOCTYPE html>
004<html lang="zh-CN">
005<head>
006<meta charset="UTF-8">
007<meta name="renderer" content="webkit">
008<meta http-equiv="X-UA-Compatible" content="IE=edge">
009<meta name="viewport" content="initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
010<title>抽奖</title>
012<style>
013.turntable-canvas {
014    position: relative;
015}
016.turntable-canvas-bg {
017    top: 0;
018    left: 0;
019    position: absolute;
020    z-index: 1;
021}
022.turntable-canvas-content {
023    top: 0;
024    left: 0;
025    position: absolute;
026    z-index: 2;
027}
028.turntable-canvas-zhizhen {
029    position: absolute;
030    top: 0;
031    bottom: 0;
032    left: 0;
033    right: 0;
034    margin: auto;
035    z-index: 3;
036    cursor: pointer;
037}
038</style>
039</head>
040<body ontouchstart>
041<div id="turntableCanvas"></div>
042<script>
043var TurntableCanvas = {
044    go(dom, config, get, cb) {
045        this.dom = dom;
046        this.width = config.width || 300;
047        this.height = config.width || 300;
048        this.boundary = config.boundary || 1;
049        this.zhizhen = {
050            width: config.zhizhen && config.zhizhen.width || 50,
051            src: config.zhizhen && config.zhizhen.src || 'http://www.5imoban.net/view/demoimg/choujiang-arrow.png'
052        };
053        this.bg = {
054            width: config.bg && config.bg.width || 20,
055            color: config.bg && config.bg.color || 'rgb(255,185,74)',
056            lampNum: config.bg && config.bg.lampNum || 12,
057            twinkleType: config.bg && config.bg.twinkleType || 0,
058            lampColor: config.bg && config.bg.lampColor || ['rgb(255,255,255)', 'rgb(255,234,119)'],
059            lampRadius: config.bg && config.bg.lampRadius || 3,
060            twinkleTime: config.bg && config.bg.twinkleTime || 500
061        };
062        this.prize = {
063            bgColor: config.prize && config.prize.bgColor || ['rgb(255,233,204)', 'rgb(255,247,235)'],
064            textColor: config.prize && config.prize.textColor || 'rgb(214,155,94)',
065            textStyle: config.prize && config.prize.textStyle || "16px Georgia",
066            textTop: config.prize && config.prize.textTop || 14,
067            imgTop: config.prize && config.prize.imgTop || 40,
068            imgWidth: config.prize && config.prize.imgWidth || 32,
069            imgHeight: config.prize && config.prize.imgHeight || 32
070        };
071        this.prizeList = config.prizeList;
072        this.deviation = 0;
073        for (var i = 0; i < this.prizeList.length;) {
074            if (i * 360 / this.prizeList.length > 270) {
075                this.deviation = i * 360 / this.prizeList.length - 270;
076                break;
077            } else {
078                i++;
079            }
080        }
081 
082        this.rotate = 180 / this.prizeList.length - this.deviation;
083        this.get = get;
084        this.cb = cb;
085        this.init();
086    },
087    init() {
088        this.time = new Date().getTime();
089        this.dom.classList.add("turntable-canvas");
090        this.dom.style['width'] = this.width + 'px';
091        this.dom.style['height'] = this.height + 'px';
092        this.dom.innerHTML = '<canvas class="turntable-canvas-bg ' + this.time + 'bg" width="' + this.width + '" height="' + this.height + '"></canvas><canvas class="turntable-canvas-content ' + this.time + 'content" width="' + this.width + '" height="' + this.height + '"></canvas><img class="turntable-canvas-zhizhen" width="' + this.zhizhen.width + '"src="' + this.zhizhen.src + '">';
093 
094        this.bgCanvas = document.getElementsByClassName(this.time + 'bg')[0];
095        this.bgCanvasContext = this.bgCanvas.getContext('2d');
096        this.contentCanvas = document.getElementsByClassName(this.time + 'content')[0];
097        this.contentCanvasContext = this.contentCanvas.getContext('2d');
098        this.contentCanvas.style.transform = 'rotate(' + this.rotate + 'deg)';
099 
100        document.getElementsByClassName('turntable-canvas-zhizhen')[0].onclick = () => {
101            this.start();
102        }
103 
104        this.t = 0;
105        this.drawBg();
106        this.drawContent();
107    },
108    drawBg() {
109        //绘制背景
110        this.bgCanvasContext.clearRect(0, 0, this.width, this.height);
111        this.bgCanvasContext.beginPath();
112        this.bgCanvasContext.fillStyle = this.bg.color;
113        this.bgCanvasContext.arc(this.width / 2, this.height / 2, this.width / 2, 0, 2 * Math.PI);
114        this.bgCanvasContext.fill();
115        this.bgCanvasContext.closePath();
116        //绘制灯
117        //      if(this.bg.twinkleType == 0){
118        for (var i = 0; i < this.bg.lampNum; i++) {
119            this.bgCanvasContext.beginPath();
120            this.bgCanvasContext.fillStyle = this.bg.lampColor[(i + this.t) % 2];
121            this.bgCanvasContext.arc(this.width / 2 + (this.width / 2 - this.bg.width / 2) * Math.cos(360 / this.bg.lampNum * i * Math.PI / 180), this.height / 2 + (this.height / 2 - this.bg.width / 2) * Math.sin(360 / this.bg.lampNum * i * Math.PI / 180), this.bg.lampRadius, 0, 2 * Math.PI);
122            this.bgCanvasContext.fill();
123            this.bgCanvasContext.closePath();
124        }
125        //      }
126    },
127    drawContent() {
128        //奖品扇形
129        for (var i = 0; i < this.prizeList.length; i++) {
130            this.contentCanvasContext.beginPath();
131 
132            this.contentCanvasContext.moveTo(this.width / 2, this.height / 2);
133            this.contentCanvasContext.lineTo(this.width / 2 + (this.width / 2 - this.bg.width) * Math.cos(360 / this.prizeList.length * i * Math.PI / 180), this.height / 2 + (this.height / 2 - this.bg.width) * Math.sin(360 / this.prizeList.length * i * Math.PI / 180));
134 
135            this.contentCanvasContext.arc(this.width / 2, this.height / 2, this.width / 2 - this.bg.width, 360 / this.prizeList.length * Math.PI / 180 * i, 360 / this.prizeList.length * Math.PI / 180 * (i + 1));
136 
137            this.contentCanvasContext.moveTo(this.width / 2, this.height / 2);
138            this.contentCanvasContext.lineTo(this.width / 2 + (this.width / 2 - this.bg.width) * Math.cos(360 / this.prizeList.length * (i + 1) * Math.PI / 180), this.height / 2 + (this.height / 2 - this.bg.width) * Math.sin(360 / this.prizeList.length * (i + 1) * Math.PI / 180));
139 
140            this.contentCanvasContext.fillStyle = this.prize.bgColor[i % this.prize.bgColor.length];
141 
142            this.contentCanvasContext.fill();
143            this.contentCanvasContext.closePath();
144        }
145        //绘制文字和图片
146        let img = [];
147        for (let i = 0; i < this.prizeList.length; i++) {
148            if (this.prizeList[i].imgurl) {
149                img[i] = document.createElement("img");
150                img[i].src = this.prizeList[i].imgurl;
151                img[i].onload = () => {
152                    this.contentCanvasContext.save();
153                    this.contentCanvasContext.beginPath();
154 
155                    this.contentCanvasContext.translate(this.width / 2, this.height / 2);
156                    this.contentCanvasContext.rotate((this.deviation - 360 / (this.prizeList.length * 2) * (i * 2 + 1)) * Math.PI / 180);
157                    this.contentCanvasContext.translate(-this.width / 2, -this.height / 2);
158 
159                    this.contentCanvasContext.fillStyle = this.prize.textColor;
160                    this.contentCanvasContext.font = this.prize.textStyle;
161                    this.contentCanvasContext.textAlign = 'center';
162                    this.contentCanvasContext.textBaseline = 'top';
163                    this.contentCanvasContext.fillText(this.prizeList[i].text, this.width / 2, this.bg.width + this.prize.textTop);
164 
165                    this.contentCanvasContext.drawImage(img[i], this.width / 2 - this.prize.imgWidth / 2, this.bg.width + this.prize.imgTop, this.prize.imgWidth, this.prize.imgHeight)
166                    this.contentCanvasContext.closePath();
167                    this.contentCanvasContext.restore();
168                }
169            } else {
170                this.contentCanvasContext.save();
171                this.contentCanvasContext.beginPath();
172 
173                this.contentCanvasContext.translate(this.width / 2, this.height / 2);
174                this.contentCanvasContext.rotate((this.deviation - 360 / (this.prizeList.length * 2) * (i * 2 + 1)) * Math.PI / 180);
175                this.contentCanvasContext.translate(-this.width / 2, -this.height / 2);
176 
177                this.contentCanvasContext.fillStyle = this.prize.textColor;
178                this.contentCanvasContext.font = this.prize.textStyle;
179                this.contentCanvasContext.textAlign = 'center';
180                this.contentCanvasContext.textBaseline = 'top';
181                this.contentCanvasContext.fillText(this.prizeList[i].text, this.width / 2, this.bg.width + (this.prize.textTop * 3));
182 
183                this.contentCanvasContext.closePath();
184                this.contentCanvasContext.restore();
185            }
186        }
187    },
188    start() {
189        this.timerApi = setInterval(() => {
190            this.rotate += 40;
191            this.contentCanvas.style.transform = 'rotate(' + this.rotate + 'deg)';
192        }, 100)
193        this.timerTimeout = setInterval(() => {
194            this.t = this.t == 1 ? 0 : 1;
195            this.drawBg();
196        }, this.bg.twinkleTime)
197        this.get((i) => {
198            clearInterval(this.timerApi);
199            this.rotate = 0;
200            this.rotateEnd = 360 * 2 + this.rand(360 / this.prizeList.length * i + this.boundary, 360 / this.prizeList.length * (i + 1) - this.boundary) - this.deviation;
201            var speed = Math.ceil(((this.rotateEnd - this.rotate) / 20));
202            // console.log(this.rotateEnd, speed);
203            this.timerInterval = setInterval(() => {
204                speed = Math.ceil(((this.rotateEnd - this.rotate) / 20));
205                // console.log(this.data.rotate, speed)
206                if (this.rotate + speed >= this.rotateEnd) {
207                    this.rotate = this.rotateEnd % 360;
208                    this.rotateEnd = this.rotateEnd % 360;
209                    var prize = (Math.floor((Math.abs(this.rotate) + this.deviation) / (360 / this.prizeList.length)));
210                    if (prize == this.prizeList.length) {
211                        prize = 0;
212                    }
213 
214                    this.cb(this.prizeList[prize]);
215 
216                    clearInterval(this.timerInterval);
217                    clearTimeout(this.timerTimeout);
218                } else {
219                    this.rotate = this.rotate + speed;
220                }
221                this.contentCanvas.style.transform = 'rotate(' + this.rotate + 'deg)';
222            }, 100)
223        })
224    },
225    rand(n, m) {
226        var c = m - n + 1;
227        return Math.floor(Math.random() * c + n);
228    }
229}
230 
231function initChoujiang(goodsArr, cw, ch, headers) {
232    var TurntableCanvasConfig = {
233        //canvas宽高
234        width: cw,
235        height: ch,
236        //防止停止旋转时压住扇形边线,可适当加大
237        boundary: 1,
238        //指针图片的宽及地址
239        zhizhen: {
240            width: 50,
241            src: ''
242        },
243        //转盘背景配置
244        bg: {
245            width: 20,
246            color: 'rgb(255,185,74)',
247            lampNum: 12,
248            lampColor: ['rgb(255,255,255)', 'rgb(255,234,119)'],
249            lampRadius: 3,
250            twinkleTime: 500
251        },
252        //转盘奖品配置
253        prize: {
254            bgColor: ['rgb(255,233,204)', 'rgb(255,247,235)'],
255            textColor: 'rgb(214,155,94)',
256            textStyle: "16px Georgia",
257            textTop: 14,
258            imgTop: 40,
259            imgWidth: 32,
260            imgHeight: 32
261        },
262        //奖品列表
263        prizeList: goodsArr
264    }
265 
266    var goodsName = '';
267    TurntableCanvas.go(document.querySelector('#turntableCanvas'), TurntableCanvasConfig, (callback) => {
268        //请求接口
269        axios.get('${config.baseUrl}/turntablePrize/luckDraw', {
270            headers: headers
271        }).then(function(response) {
272            var res = response.data;
273            if (res.code === 200) {
274                if (res.data.code !== 200) {
275                    //请求失败,向native页面发送通知,给用户提示
276                    window.ReactNativeWebView.postMessage(JSON.stringify({
277                        code: '-1',
278                        msg: res.data.msg
279                    }));
280                } else {
281                    //请求成功,通过后台返回的中奖ID,匹配奖品在列表中的索引,通过callback回调,来实现转盘停在中奖索引指向的产品,同时给goodsName赋值
282                    var id = res.data.content;
283                    var idx = '';
284                    goodsArr.forEach((item, index) => {
285                        if (item.id === id) {
286                            idx = index;
287                            goodsName = item.text;
288                        }
289                    })
290                    //抽奖结果
291                    callback(idx);
292                }
293            } else {
294                //请求失败,向native页面发送通知,给用户提示
295                window.ReactNativeWebView.postMessage(JSON.stringify({
296                    code: '-1',
297                    msg: '获取结果失败!'
298                }));
299            }
300        }).catch(function(error) {
301            alert(JSON.stringify(error))
302            //接口调用失败,向native页面发送通知,给用户提示
303            window.ReactNativeWebView.postMessage(JSON.stringify({
304                code: '-1',
305                msg: '接口调用失败!'
306            }));
307        });
308    }, (res) => {
309        //转盘停止转动,向native页面发送通知,提示用户抽中了 goodsName。
310        window.ReactNativeWebView.postMessage(JSON.stringify({
311            code: '200',
312            msg: '抽奖成功!',
313            name: goodsName
314        }));
315    })
316}
317</script>
318</body>
319</html>
320`;
321export default html;
2、在需要展示抽奖的页面引入modal,引入刚才的js,在引入相关的组件方法等
1import storage from '../../utils/storage';:
2import {Platform, ImageBackground, ScrollView, StyleSheet, Animated, Easing, Dimensions} from 'react-native';
3import Modal from 'react-native-modal';
4import html from './choujiang';
5import Icon from 'react-native-vector-icons/AntDesign';
3、定义抽奖弹窗showModel、获取后台的奖品列表数据、定义注入的js,来调用上面的html的js方法,传入列表、宽高和head请求头等。
01/***********************接受抽奖返回的数据*******************/
02// 奖品列表的格式
03// [{
04//   "id": 1,
05//   "text": "1元红包",
06//   "img": "aa18972bd40735fa8026091694510fb30e24084e.jpg"
07// }]
08const [INJECTEDJAVASCRIPT, SetINJECTEDJAVASCRIPT] = useState('');
09 
10//抽奖列表
11const getTurntableListData = async () => {
12  //获取接口
13  const res = await getTurntableList();
14  let arr = res.data.map(item => {
15    let temp = {};
16    if(item.pic){
17      temp = {
18        id: item.id,
19        img: item.pic
20      }
21    }else{
22      temp = {
23        id: item.id,
24        text: item.prizeName
25      }
26    }
27    return temp;
28  })
29  if(arr.length <2){
30    Toast.show({type: 'error', content: '没有可抽奖的产品'});
31    return;
32  }else{
33    var winWidth = Dimensions.get('window').width;
34    // 获取用户信息,得到请求头,给webview页面的axios使用
35    const currentUser = await storage.getCurrentUser();
36    const headers = {
37      'ibic-token': currentUser ? currentUser.accessToken : '',
38      'ibic-client': Platform.select({ios: 2, android: 1}),
39      'ibic-version': 1,
40      'Accept-Language': 'zh-CN', // zh-CN 中文,en-US 英文
41    };
42    SetINJECTEDJAVASCRIPT(`initChoujiang(${JSON.stringify(arr)},${winWidth-30},${winWidth-30},${JSON.stringify(headers)})`);
43  }
44}
45 
46//调用奖品列表
47useEffect(() => {
48  getTurntableListData();
49}, [getTurntableListData]);
50 
51// 监听函数,从webview的抽奖方法返回,可能失败,可能成功
52const _onMessage = (data) => {
53  //{"nativeEvent":{"data":"抽奖成功了!!","canGoForward":false,"loading":false,"title":"GB Canvas Turntable","canGoBack":false,"url":"about:blank","target":1777}}
54  console.log('抽奖结果:',JSON.stringify(data));
55  let res = JSON.parse(data.nativeEvent.data);
56  if(res.code !== '200'){
57    Toast.show({type: 'error', content: res.msg});
58  }else{
59    Toast.show({type: 'success', content: '恭喜,您抽中了'+res.name+'!'});
60    //更新用户积分
61    getUserInfo();
62  }
63}
4、点击弹出抽奖的按钮
1<Item assetName="jfcj" text="积分抽奖" onPress={()=> { setShowModel(true) }}/>
5、抽奖modal
01<Modal isVisible={showModel} style={styles.modal}>
02    <View style={styles.modalWrap}>
03        <TouchableOpacity right marginR-30 onPress={ () =>{ setShowModel(false) } }>
04            <Icon name="close" size={30} style={{color: '#fff' }} />
05        </TouchableOpacity>
06        <WebView
07            style={{backgroundColor:'transparent'}}
08            originWhitelist={['*']}
09            source={{html}}
10            onMessage={_onMessage}
11            injectedJavaScript={INJECTEDJAVASCRIPT}
12        />
13    </View>
14</Modal>
7、css样式:
01const styles = StyleSheet.create({
02  modal: {
03    justifyContent: 'center',
04    alignItems: 'center',
05    margin: 0,
06  },
07  modalWrap:{
08    width:Dimensions.get('window').width,
09    height:Dimensions.get('window').width+30,
10  },
11});
终于非常完美的实现了。