我爱模板网在做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,然后传给回调,转盘转动结束,就会停留在后台指定的中奖区域。在中奖回调中,用
1 | window.ReactNativeWebView.postMessage(data) |
的方法向native页面发送消息:
001 | import config from '../../utils/config' ; |
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" > |
016 | .turntable-canvas-bg { |
022 | .turntable-canvas-content { |
028 | .turntable-canvas-zhizhen { |
041 | <div id= "turntableCanvas" ></div> |
043 | var TurntableCanvas = { |
044 | go(dom, config, get, cb) { |
046 | this .width = config.width || 300; |
047 | this .height = config.width || 300; |
048 | this .boundary = config.boundary || 1; |
050 | width: config.zhizhen && config.zhizhen.width || 50, |
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 |
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 |
071 | this .prizeList = config.prizeList; |
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; |
082 | this .rotate = 180 / this .prizeList.length - this .deviation; |
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 + '">' ; |
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)' ; |
100 | document.getElementsByClassName( 'turntable-canvas-zhizhen' )[0].onclick = () => { |
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(); |
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(); |
129 | for ( var i = 0; i < this .prizeList.length; i++) { |
130 | this .contentCanvasContext.beginPath(); |
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)); |
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)); |
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)); |
140 | this .contentCanvasContext.fillStyle = this .prize.bgColor[i % this .prize.bgColor.length]; |
142 | this .contentCanvasContext.fill(); |
143 | this .contentCanvasContext.closePath(); |
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(); |
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); |
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); |
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(); |
170 | this .contentCanvasContext.save(); |
171 | this .contentCanvasContext.beginPath(); |
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); |
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)); |
183 | this .contentCanvasContext.closePath(); |
184 | this .contentCanvasContext.restore(); |
189 | this .timerApi = setInterval(() => { |
191 | this .contentCanvas.style.transform = 'rotate(' + this .rotate + 'deg)' ; |
193 | this .timerTimeout = setInterval(() => { |
194 | this .t = this .t == 1 ? 0 : 1; |
196 | }, this .bg.twinkleTime) |
198 | clearInterval( this .timerApi); |
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)); |
203 | this .timerInterval = setInterval(() => { |
204 | speed = Math.ceil((( this .rotateEnd - this .rotate) / 20)); |
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) { |
214 | this .cb( this .prizeList[prize]); |
216 | clearInterval( this .timerInterval); |
217 | clearTimeout( this .timerTimeout); |
219 | this .rotate = this .rotate + speed; |
221 | this .contentCanvas.style.transform = 'rotate(' + this .rotate + 'deg)' ; |
227 | return Math.floor(Math.random() * c + n); |
231 | function initChoujiang(goodsArr, cw, ch, headers) { |
232 | var TurntableCanvasConfig = { |
246 | color: 'rgb(255,185,74)' , |
248 | lampColor: [ 'rgb(255,255,255)' , 'rgb(255,234,119)' ], |
254 | bgColor: [ 'rgb(255,233,204)' , 'rgb(255,247,235)' ], |
255 | textColor: 'rgb(214,155,94)' , |
256 | textStyle: "16px Georgia" , |
267 | TurntableCanvas.go(document.querySelector( '#turntableCanvas' ), TurntableCanvasConfig, (callback) => { |
269 | axios.get( '${config.baseUrl}/turntablePrize/luckDraw' , { |
271 | }).then( function (response) { |
272 | var res = response.data; |
273 | if (res.code === 200) { |
274 | if (res.data.code !== 200) { |
276 | window.ReactNativeWebView.postMessage(JSON.stringify({ |
282 | var id = res.data.content; |
284 | goodsArr.forEach((item, index) => { |
285 | if (item.id === id) { |
287 | goodsName = item.text; |
295 | window.ReactNativeWebView.postMessage(JSON.stringify({ |
300 | }). catch ( function (error) { |
301 | alert(JSON.stringify(error)) |
303 | window.ReactNativeWebView.postMessage(JSON.stringify({ |
310 | window.ReactNativeWebView.postMessage(JSON.stringify({ |
2、在需要展示抽奖的页面引入modal,引入刚才的js,在引入相关的组件方法等
1 | import storage from '../../utils/storage' ;: |
2 | import {Platform, ImageBackground, ScrollView, StyleSheet, Animated, Easing, Dimensions} from 'react-native' ; |
3 | import Modal from 'react-native-modal' ; |
4 | import html from './choujiang' ; |
5 | import Icon from 'react-native-vector-icons/AntDesign' ; |
3、定义抽奖弹窗showModel、获取后台的奖品列表数据、定义注入的js,来调用上面的html的js方法,传入列表、宽高和head请求头等。
08 | const [INJECTEDJAVASCRIPT, SetINJECTEDJAVASCRIPT] = useState( '' ); |
11 | const getTurntableListData = async () => { |
13 | const res = await getTurntableList(); |
14 | let arr = res.data.map(item => { |
30 | Toast.show({type: 'error' , content: '没有可抽奖的产品' }); |
33 | var winWidth = Dimensions.get( 'window' ).width; |
35 | const currentUser = await storage.getCurrentUser(); |
37 | 'ibic-token' : currentUser ? currentUser.accessToken : '' , |
38 | 'ibic-client' : Platform.select({ios: 2, android: 1}), |
40 | 'Accept-Language' : 'zh-CN' , |
42 | SetINJECTEDJAVASCRIPT(`initChoujiang(${JSON.stringify(arr)},${winWidth-30},${winWidth-30},${JSON.stringify(headers)})`); |
48 | getTurntableListData(); |
49 | }, [getTurntableListData]); |
52 | const _onMessage = (data) => { |
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}); |
59 | Toast.show({type: 'success' , content: '恭喜,您抽中了' +res.name+ '!' }); |
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' }} /> |
07 | style={{backgroundColor:'transparent'}} |
08 | originWhitelist={['*']} |
10 | onMessage={_onMessage} |
11 | injectedJavaScript={INJECTEDJAVASCRIPT} |
7、css样式:
01 | const styles = StyleSheet.create({ |
03 | justifyContent: 'center' , |
08 | width:Dimensions.get( 'window' ).width, |
09 | height:Dimensions.get( 'window' ).width+30, |
终于非常完美的实现了。