首页 > 建站教程 > 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,然后传给回调,转盘转动结束,就会停留在后台指定的中奖区域。在中奖回调中,用
window.ReactNativeWebView.postMessage(data)
的方法向native页面发送消息:
import config from '../../utils/config';
const html = `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>抽奖</title>
<script src="http://www.5imoban.net/uploads/allimg/210514/1531434960-1.jpg"></script>
<style>
.turntable-canvas {
    position: relative;
}
.turntable-canvas-bg {
    top: 0;
    left: 0;
    position: absolute;
    z-index: 1;
}
.turntable-canvas-content {
    top: 0;
    left: 0;
    position: absolute;
    z-index: 2;
}
.turntable-canvas-zhizhen {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
    z-index: 3;
    cursor: pointer;
}
</style>
</head>
<body ontouchstart>
<div id="turntableCanvas"></div>
<script>
var TurntableCanvas = {
    go(dom, config, get, cb) {
        this.dom = dom;
        this.width = config.width || 300;
        this.height = config.width || 300;
        this.boundary = config.boundary || 1;
        this.zhizhen = {
            width: config.zhizhen && config.zhizhen.width || 50,
            src: config.zhizhen && config.zhizhen.src || 'http://www.5imoban.net/view/demoimg/choujiang-arrow.png'
        };
        this.bg = {
            width: config.bg && config.bg.width || 20,
            color: config.bg && config.bg.color || 'rgb(255,185,74)',
            lampNum: config.bg && config.bg.lampNum || 12,
            twinkleType: config.bg && config.bg.twinkleType || 0,
            lampColor: config.bg && config.bg.lampColor || ['rgb(255,255,255)', 'rgb(255,234,119)'],
            lampRadius: config.bg && config.bg.lampRadius || 3,
            twinkleTime: config.bg && config.bg.twinkleTime || 500
        };
        this.prize = {
            bgColor: config.prize && config.prize.bgColor || ['rgb(255,233,204)', 'rgb(255,247,235)'],
            textColor: config.prize && config.prize.textColor || 'rgb(214,155,94)',
            textStyle: config.prize && config.prize.textStyle || "16px Georgia",
            textTop: config.prize && config.prize.textTop || 14,
            imgTop: config.prize && config.prize.imgTop || 40,
            imgWidth: config.prize && config.prize.imgWidth || 32,
            imgHeight: config.prize && config.prize.imgHeight || 32
        };
        this.prizeList = config.prizeList;
        this.deviation = 0;
        for (var i = 0; i < this.prizeList.length;) {
            if (i * 360 / this.prizeList.length > 270) {
                this.deviation = i * 360 / this.prizeList.length - 270;
                break;
            } else {
                i++;
            }
        }

        this.rotate = 180 / this.prizeList.length - this.deviation;
        this.get = get;
        this.cb = cb;
        this.init();
    },
    init() {
        this.time = new Date().getTime();
        this.dom.classList.add("turntable-canvas");
        this.dom.style['width'] = this.width + 'px';
        this.dom.style['height'] = this.height + 'px';
        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 + '">';

        this.bgCanvas = document.getElementsByClassName(this.time + 'bg')[0];
        this.bgCanvasContext = this.bgCanvas.getContext('2d');
        this.contentCanvas = document.getElementsByClassName(this.time + 'content')[0];
        this.contentCanvasContext = this.contentCanvas.getContext('2d');
        this.contentCanvas.style.transform = 'rotate(' + this.rotate + 'deg)';

        document.getElementsByClassName('turntable-canvas-zhizhen')[0].onclick = () => {
            this.start();
        }

        this.t = 0;
        this.drawBg();
        this.drawContent();
    },
    drawBg() {
        //绘制背景
        this.bgCanvasContext.clearRect(0, 0, this.width, this.height);
        this.bgCanvasContext.beginPath();
        this.bgCanvasContext.fillStyle = this.bg.color;
        this.bgCanvasContext.arc(this.width / 2, this.height / 2, this.width / 2, 0, 2 * Math.PI);
        this.bgCanvasContext.fill();
        this.bgCanvasContext.closePath();
        //绘制灯
        //      if(this.bg.twinkleType == 0){
        for (var i = 0; i < this.bg.lampNum; i++) {
            this.bgCanvasContext.beginPath();
            this.bgCanvasContext.fillStyle = this.bg.lampColor[(i + this.t) % 2];
            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);
            this.bgCanvasContext.fill();
            this.bgCanvasContext.closePath();
        }
        //      }
    },
    drawContent() {
        //奖品扇形
        for (var i = 0; i < this.prizeList.length; i++) {
            this.contentCanvasContext.beginPath();

            this.contentCanvasContext.moveTo(this.width / 2, this.height / 2);
            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));

            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));

            this.contentCanvasContext.moveTo(this.width / 2, this.height / 2);
            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));

            this.contentCanvasContext.fillStyle = this.prize.bgColor[i % this.prize.bgColor.length];

            this.contentCanvasContext.fill();
            this.contentCanvasContext.closePath();
        }
        //绘制文字和图片
        let img = [];
        for (let i = 0; i < this.prizeList.length; i++) {
            if (this.prizeList[i].imgurl) {
                img[i] = document.createElement("img");
                img[i].src = this.prizeList[i].imgurl;
                img[i].onload = () => {
                    this.contentCanvasContext.save();
                    this.contentCanvasContext.beginPath();

                    this.contentCanvasContext.translate(this.width / 2, this.height / 2);
                    this.contentCanvasContext.rotate((this.deviation - 360 / (this.prizeList.length * 2) * (i * 2 + 1)) * Math.PI / 180);
                    this.contentCanvasContext.translate(-this.width / 2, -this.height / 2);

                    this.contentCanvasContext.fillStyle = this.prize.textColor;
                    this.contentCanvasContext.font = this.prize.textStyle;
                    this.contentCanvasContext.textAlign = 'center';
                    this.contentCanvasContext.textBaseline = 'top';
                    this.contentCanvasContext.fillText(this.prizeList[i].text, this.width / 2, this.bg.width + this.prize.textTop);

                    this.contentCanvasContext.drawImage(img[i], this.width / 2 - this.prize.imgWidth / 2, this.bg.width + this.prize.imgTop, this.prize.imgWidth, this.prize.imgHeight)
                    this.contentCanvasContext.closePath();
                    this.contentCanvasContext.restore();
                }
            } else {
                this.contentCanvasContext.save();
                this.contentCanvasContext.beginPath();

                this.contentCanvasContext.translate(this.width / 2, this.height / 2);
                this.contentCanvasContext.rotate((this.deviation - 360 / (this.prizeList.length * 2) * (i * 2 + 1)) * Math.PI / 180);
                this.contentCanvasContext.translate(-this.width / 2, -this.height / 2);

                this.contentCanvasContext.fillStyle = this.prize.textColor;
                this.contentCanvasContext.font = this.prize.textStyle;
                this.contentCanvasContext.textAlign = 'center';
                this.contentCanvasContext.textBaseline = 'top';
                this.contentCanvasContext.fillText(this.prizeList[i].text, this.width / 2, this.bg.width + (this.prize.textTop * 3));

                this.contentCanvasContext.closePath();
                this.contentCanvasContext.restore();
            }
        }
    },
    start() {
        this.timerApi = setInterval(() => {
            this.rotate += 40;
            this.contentCanvas.style.transform = 'rotate(' + this.rotate + 'deg)';
        }, 100)
        this.timerTimeout = setInterval(() => {
            this.t = this.t == 1 ? 0 : 1;
            this.drawBg();
        }, this.bg.twinkleTime)
        this.get((i) => {
            clearInterval(this.timerApi);
            this.rotate = 0;
            this.rotateEnd = 360 * 2 + this.rand(360 / this.prizeList.length * i + this.boundary, 360 / this.prizeList.length * (i + 1) - this.boundary) - this.deviation;
            var speed = Math.ceil(((this.rotateEnd - this.rotate) / 20));
            // console.log(this.rotateEnd, speed);
            this.timerInterval = setInterval(() => {
                speed = Math.ceil(((this.rotateEnd - this.rotate) / 20));
                // console.log(this.data.rotate, speed)
                if (this.rotate + speed >= this.rotateEnd) {
                    this.rotate = this.rotateEnd % 360;
                    this.rotateEnd = this.rotateEnd % 360;
                    var prize = (Math.floor((Math.abs(this.rotate) + this.deviation) / (360 / this.prizeList.length)));
                    if (prize == this.prizeList.length) {
                        prize = 0;
                    }

                    this.cb(this.prizeList[prize]);

                    clearInterval(this.timerInterval);
                    clearTimeout(this.timerTimeout);
                } else {
                    this.rotate = this.rotate + speed;
                }
                this.contentCanvas.style.transform = 'rotate(' + this.rotate + 'deg)';
            }, 100)
        })
    },
    rand(n, m) {
        var c = m - n + 1;
        return Math.floor(Math.random() * c + n);
    }
}

function initChoujiang(goodsArr, cw, ch, headers) {
    var TurntableCanvasConfig = {
        //canvas宽高
        width: cw,
        height: ch,
        //防止停止旋转时压住扇形边线,可适当加大
        boundary: 1,
        //指针图片的宽及地址
        zhizhen: {
            width: 50,
            src: ''
        },
        //转盘背景配置
        bg: {
            width: 20,
            color: 'rgb(255,185,74)',
            lampNum: 12,
            lampColor: ['rgb(255,255,255)', 'rgb(255,234,119)'],
            lampRadius: 3,
            twinkleTime: 500
        },
        //转盘奖品配置
        prize: {
            bgColor: ['rgb(255,233,204)', 'rgb(255,247,235)'],
            textColor: 'rgb(214,155,94)',
            textStyle: "16px Georgia",
            textTop: 14,
            imgTop: 40,
            imgWidth: 32,
            imgHeight: 32
        },
        //奖品列表
        prizeList: goodsArr
    }

    var goodsName = '';
    TurntableCanvas.go(document.querySelector('#turntableCanvas'), TurntableCanvasConfig, (callback) => {
        //请求接口
        axios.get('${config.baseUrl}/turntablePrize/luckDraw', {
            headers: headers
        }).then(function(response) {
            var res = response.data;
            if (res.code === 200) {
                if (res.data.code !== 200) {
                    //请求失败,向native页面发送通知,给用户提示
                    window.ReactNativeWebView.postMessage(JSON.stringify({
                        code: '-1',
                        msg: res.data.msg
                    }));
                } else {
                    //请求成功,通过后台返回的中奖ID,匹配奖品在列表中的索引,通过callback回调,来实现转盘停在中奖索引指向的产品,同时给goodsName赋值
                    var id = res.data.content;
                    var idx = '';
                    goodsArr.forEach((item, index) => {
                        if (item.id === id) {
                            idx = index;
                            goodsName = item.text;
                        }
                    })
                    //抽奖结果
                    callback(idx);
                }
            } else {
                //请求失败,向native页面发送通知,给用户提示
                window.ReactNativeWebView.postMessage(JSON.stringify({
                    code: '-1',
                    msg: '获取结果失败!'
                }));
            }
        }).catch(function(error) {
            alert(JSON.stringify(error))
            //接口调用失败,向native页面发送通知,给用户提示
            window.ReactNativeWebView.postMessage(JSON.stringify({
                code: '-1',
                msg: '接口调用失败!'
            }));
        });
    }, (res) => {
        //转盘停止转动,向native页面发送通知,提示用户抽中了 goodsName。
        window.ReactNativeWebView.postMessage(JSON.stringify({
            code: '200',
            msg: '抽奖成功!',
            name: goodsName
        }));
    })
}
</script>
</body>
</html>
`;
export default html;
2、在需要展示抽奖的页面引入modal,引入刚才的js,在引入相关的组件方法等
import storage from '../../utils/storage';:
import {Platform, ImageBackground, ScrollView, StyleSheet, Animated, Easing, Dimensions} from 'react-native';
import Modal from 'react-native-modal';
import html from './choujiang';
import Icon from 'react-native-vector-icons/AntDesign';
3、定义抽奖弹窗showModel、获取后台的奖品列表数据、定义注入的js,来调用上面的html的js方法,传入列表、宽高和head请求头等。
  /***********************接受抽奖返回的数据*******************/
  // 奖品列表的格式
  // [{
  //   "id": 1,
  //   "text": "1元红包",
  //   "img": "aa18972bd40735fa8026091694510fb30e24084e.jpg"
  // }]
  const [INJECTEDJAVASCRIPT, SetINJECTEDJAVASCRIPT] = useState('');

  //抽奖列表
  const getTurntableListData = async () => {
    //获取接口
    const res = await getTurntableList();
    let arr = res.data.map(item => {
      let temp = {};
      if(item.pic){
        temp = {
          id: item.id,
          img: item.pic
        }
      }else{
        temp = {
          id: item.id,
          text: item.prizeName
        }
      }
      return temp;
    })
    if(arr.length <2){
      Toast.show({type: 'error', content: '没有可抽奖的产品'});
      return;
    }else{
      var winWidth = Dimensions.get('window').width;
      // 获取用户信息,得到请求头,给webview页面的axios使用
      const currentUser = await storage.getCurrentUser();
      const headers = {
        'ibic-token': currentUser ? currentUser.accessToken : '',
        'ibic-client': Platform.select({ios: 2, android: 1}),
        'ibic-version': 1,
        'Accept-Language': 'zh-CN', // zh-CN 中文,en-US 英文
      };
      SetINJECTEDJAVASCRIPT(`initChoujiang(${JSON.stringify(arr)},${winWidth-30},${winWidth-30},${JSON.stringify(headers)})`);
    }
  }

  //调用奖品列表
  useEffect(() => {
    getTurntableListData();
  }, [getTurntableListData]);

  // 监听函数,从webview的抽奖方法返回,可能失败,可能成功
  const _onMessage = (data) => {
    //{"nativeEvent":{"data":"抽奖成功了!!","canGoForward":false,"loading":false,"title":"GB Canvas Turntable","canGoBack":false,"url":"about:blank","target":1777}}
    console.log('抽奖结果:',JSON.stringify(data));
    let res = JSON.parse(data.nativeEvent.data);
    if(res.code !== '200'){
      Toast.show({type: 'error', content: res.msg});
    }else{
      Toast.show({type: 'success', content: '恭喜,您抽中了'+res.name+'!'});
      //更新用户积分
      getUserInfo();
    }
  }
4、点击弹出抽奖的按钮
<Item assetName="jfcj" text="积分抽奖" onPress={()=> { setShowModel(true) }}/>
5、抽奖modal
<Modal isVisible={showModel} style={styles.modal}>
    <View style={styles.modalWrap}>
        <TouchableOpacity right marginR-30 onPress={ () =>{ setShowModel(false) } }>
            <Icon name="close" size={30} style={{color: '#fff' }} />
        </TouchableOpacity>
        <WebView
            style={{backgroundColor:'transparent'}}
            originWhitelist={['*']}
            source={{html}}
            onMessage={_onMessage}
            injectedJavaScript={INJECTEDJAVASCRIPT}
        />
    </View>
</Modal>
7、css样式:
const styles = StyleSheet.create({
  modal: {
    justifyContent: 'center',
    alignItems: 'center',
    margin: 0,
  },
  modalWrap:{
    width:Dimensions.get('window').width,
    height:Dimensions.get('window').width+30,
  },
});
终于非常完美的实现了。