canvas之转盘抽奖

                                           canvas之转盘抽奖        

 

1.    整理

     大概思路  初始化各种参数 => 绘制外层(图片) => 绘制餐品背景加内容 => 点击按钮 => click事件 => 回调函数(旋转结束后执行)

     初始化参数: 

function initLucky(options) {
            //把所有需要的变量都放到一个对象中
            let defaultOption = initOption(options);
            drawBorder(defaultOption);
            //初始化对象 考虑是否用构造函数还是用方法来写初始化(考虑到仅仅是存点变量,还是选择方法)
            function initOption(_option) {
                console.log(document.getElementById(`${_option.id}`));
                var optionA = {
                    canvas: document.getElementById(`${_option.id}`), //对象
                    canvasW: document.getElementById(`${_option.id}`).parentNode.offsetWidth, //宽 this指向的是windows !!! 怕严格模式下 有错误改了
                    canvasH: document.getElementById(`${_option.id}`).parentNode.offsetHeight, //高
                    context: document.getElementById(`${_option.id}`).getContext("2d"), //上下文对象
                    productsList: _option.textlist, //产品列表
                    // lengthAA:  _option.textlist.length, //获取不到this.productsList为什么?? this指向的是windows !!!
                    baseRadian: Math.PI / 180 * 360 / _option.textlist.length, //默认弧度
                    backgroundImg: _option.backgroundImg, // 背景图片
                    clickImg: _option.clickImg, //点击按钮图片
                    alternateColor: _option.alternateColor, //交替颜色
                    alternateColor2: _option.alternateColor2, //交替颜色2
                    stratRadian: _option.stratRadian, //启示弧度
                    wordSub: _option.wordSub, //截取字符串得长度 (我用的 substring())
                    timer: "", // 保存 setinterval
                    roateRadian: 0, // 默认转得时候的角度
                    luckPosition: 0, //默认中奖位置
                    cacheImg: "", //canvas缓存
                    frequency: _option.frequency, //次数 限制几次抽奖
                    flagTimes: 0, //设置权限只能点击一次
                    onlyone: 0, //单击设置
                    domain: _option.domain, //点击执行接口得地址
                    result: "", //接口返回信息
                    success: _option.success ? _option.success : "", //成功回调函数
                }
                //设置canvas的宽高
                optionA.canvas.width = optionA.canvasW;
                optionA.canvas.height = optionA.canvasH;
                createCache(optionA);
                return optionA;
            } //end

 

   ,  第一次页面加载需要获取

 var option = {
                id: "mfe_extract",
                textlist: list, //turntable_list,
                backgroundImg: "./images/magpieFestival/bg.png",
                clickImg: "./images/magpieFestival/begin.png",
                alternateColor: "#eb68a3",
                alternateColor2: "#e83e95",
                stratRadian: Math.PI / 180 * 0,
                wordSub: 4,
                frequency: 5,
                domain: ``,
                success: success,
            }

      绘制外层(图片): 最先绘制外层得图片  backgrdoundImg

      绘制餐品背景加内容: 先画背景颜色(有疑问) 然后画产品{先计算产品位置,然后旋转角度,在设置字体,行高,根据字符串格式,判断如何遍历字符串}

      绘制点击按钮: 在产品完成后 最后绘制clickImg 由于渲染顺序 所以我把draw方法写在了 绘制产品得方法里面,这也造成 如果我,直接用图片路径 每次绘制会草成闪烁得情况, 所以我改用 在新建一个canvas来保存图片, 每次绘制 绘制这个canvas 闪烁效果解决, 但是后来出现火狐第一次渲染页面 点击按钮消失得情况,所以第一次绘制得时候加入 在绘制得时候 onload 之后 在绘制canvas,感觉这个办法应该不是很好,有大佬看到请解答...

      click事件: 最先判断鼠标是否再点击按钮得范围内 之后判断 旋转是否完成(有bug 谷歌下 一直单击会卡)  然后判断抽奖次数(这里因为更改需求所以逻辑有问题,没时间改就按照这个逻辑继续写了 ps.以前只让点不超过多少次,现在是免费次数加积分),由于我旋转图形,绘制得时候并没有清空,所以如果字体超出图形,会有一圈字体颜色得圈,(字体不要超出扇形得范围),执行用得setinterval 并没有用 requestAnimationFrame 有时间会改成 requestAnimationFrame, 旋转变速会有些卡顿(并不是判断setinterval得时间来控制速度,而是用每次旋转得弧度) 有时间改. 2018/7/31--- 增加 ajax错误时候的判断(跟条形码jq冲突但不影响),修改变速(依旧稍微卡顿 用的setinterval),

      回调函数(旋转结束后执行) : 之前把success写在了里面,发现很不方便,每个页面操作得dom得方式可能不一样,所以用委托得形式传方法,这样这个转盘只管绘制,不涉及操作大量操作dom 参数就是ajax返回得结果, 

2.    代码

//没整合js 还没写完  不想ajax直接在方法里写 想传参调用
function initLucky(options) {
    //把所有需要的变量都放到一个对象中
    let defaultOption = initOption(options);
    drawBorder(defaultOption);
    //初始化对象 考虑是否用构造函数还是用方法来写初始化(考虑到仅仅是存点变量,还是选择方法)
    function initOption(_option) {
        console.log(document.getElementById(`${_option.id}`));
        var optionA = {
            canvas: document.getElementById(`${_option.id}`), //对象
            canvasW: document.getElementById(`${_option.id}`).parentNode.offsetWidth, //宽 this指向的是windows !!! 怕严格模式下 有错误改了
            canvasH: document.getElementById(`${_option.id}`).parentNode.offsetHeight, //高
            context: document.getElementById(`${_option.id}`).getContext("2d"), //上下文对象
            productsList: _option.textlist, //产品列表
            // lengthAA:  _option.textlist.length, //获取不到this.productsList为什么?? this指向的是windows !!!
            baseRadian: Math.PI / 180 * 360 / _option.textlist.length, //默认弧度
            backgroundImg: _option.backgroundImg, // 背景图片
            clickImg: _option.clickImg, //点击按钮图片
            alternateColor: _option.alternateColor, //交替颜色
            alternateColor2: _option.alternateColor2, //交替颜色2
            stratRadian: _option.stratRadian, //启示弧度
            wordSub: _option.wordSub, //截取字符串得长度 (我用的 substring())
            timer: "", // 保存 setinterval
            roateRadian: 0, // 默认转得时候的角度
            luckPosition: 0, //默认中奖位置
            cacheImg: "", //canvas缓存
            frequency: _option.frequency, //次数 限制几次抽奖
            flagTimes: 0, //设置权限只能点击一次
            onlyone: 0, //单击设置
            domain: _option.domain, //点击执行接口得地址
            result: "1", //接口返回信息
            success: _option.success ? _option.success : "", //成功回调函数
        }
        //设置canvas的宽高
        optionA.canvas.width = optionA.canvasW;
        optionA.canvas.height = optionA.canvasH;
        createCache(optionA);
        return optionA;
    } //end


    //由于图片闪烁 尝试下网上说的双缓存
    function createCache(_option) {
        if (!_option.cacheImg) {
            let cacheCanvas = document.createElement("canvas");
            let cacheContext = cacheCanvas.getContext("2d");
            let img = new Image();
            img.onload = function () {
                cacheCanvas.width = _option.canvasH / 3.2 * (img.width / img.height);
                cacheCanvas.height = _option.canvasH / 3.2;
                cacheContext.drawImage(img, 0, 0,
                    _option.canvasH / 3.2 * (img.width / img.height),
                    _option.canvasH / 3.2);
                // _option.cacheImg = cacheCanvas; //如果等图片加载完在赋值会报错
            }
            img.src = _option.clickImg;
            _option.cacheImg = cacheCanvas; //这样不会报错,但是点击得图片会消失 目前只出现在火狐
        }
    } //end
    //绘制最外层边框
    function drawBorder(_option) {
        //因为插件自带提示 是context为了方便,赋值一下
        let context = _option.context;
        //让所有图形的基本点都在最中间
        context.translate(_option.canvasW / 2, _option.canvasH / 2);
        //保存
        context.save();
        context.beginPath();
        let img = new Image();
        img.onload = function () {
            if (_option.cacheImg) {
                //由于给的图形是长方形,所以我绝对已高为单位做半径
                context.drawImage(img, -_option.canvasH / 2, -_option.canvasH / 2, _option.canvasH,
                    _option
                    .canvasH);
                //ps 由于第一次加载 可能会出现图片记载不出来得情况
                //这么做感觉有点愚蠢 谁有更好办法清联系我 578024797
                let img2 = new Image();
                img2.onload = function () {
                    //本来想写在外面的 但是由于层级顺序只能写在里面了
                    drawProducts(_option);
                    //点击事件
                    registeClick(_option);
                }
                img2.src = _option.clickImg;

            }

        }
        img.src = _option.backgroundImg;

        //恢复
        context.restore();
    } //end

    //绘制产品
    function drawProducts(_option) {
        let context = _option.context;
        //保存
        for (let index = 0; index < _option.productsList.length; index++) {
            let sAngle = _option.baseRadian * index + _option.stratRadian;
            context.save();
            context.beginPath();
            context.rotate(_option.roateRadian * Math.PI / 180); //settimeout时候得角度 单纯调页面请注视
            //判断颜色
            if (index % 2 === 0) {
                context.fillStyle = _option.alternateColor;
                // context.fillStyle = "#000"; //test
            } else {
                context.fillStyle = _option.alternateColor2;
            }

            //ps 上下间距有点误差 我怎么总感觉是背景图得原因那...
            //绘制大圆
            context.arc(0, 0, _option.canvasH / 2.25, sAngle, sAngle + _option.baseRadian, false);
            //绘制小圆
            context.arc(0, 0, _option.canvasH / 6.5, sAngle + _option.baseRadian, sAngle, false);

            context.fill();
            //绘制产品
            context.fillStyle = "#fff";
            /*
                字体行高 都是根据canvas得宽来的,我并没有根据产品得数量来判断,
                一策划没给我产品个数范围,出的图也没有考虑(4个奖品还抽啥 回家过家家得了) 
                二手机页奖品超过10个 感觉会字体变得越来越小,太小导致看不清
             */
            let fontWeight = _option.canvasH / 100 * 6; //文字适配根据canvas得宽
            let lineHeight = _option.canvasH / 11; //行高
            let lineHeight2 = _option.canvasH / 13.5; //行高2
            context.font = `${fontWeight}px Microsoft YaHei`;
            let tranX = 0 + Math.cos(sAngle + _option.baseRadian / 2) * _option.canvasH / 2.25;
            let tranY = 0 + Math.sin(sAngle + _option.baseRadian / 2) * _option.canvasH / 2.25;
            context.translate(tranX, tranY);
            context.rotate(sAngle + _option.baseRadian / 2 + Math.PI / 2);
            let textTimes = Math.ceil(_option.productsList[index].length / _option.wordSub)
            //获取在字符串中得数字
            var stringContainsNumber = _option.productsList[index].replace(/[^0-9]/ig, "");
            for (let j = 0; j < textTimes; j++) {
                //概述下判断, 之前写的是根据字数判断,导致很不美观
                /* 
                    判断字符串截取位置  _option.wordSub * (j+1) 如果大于 numberIndex数字在字符串得位置
                    就认为碰到数字了(字符串只有一个数字!!!!) 然后判断_option.wordSub * j与 numberIndex 有没有
                    字符了 有就渲染出来 没有 直接 substring(numberIndex);
                    由于 第一次渲染跟第二次渲染文字得行高不同 所以判断了下 j === 0
                 */
                if (j === 0) {
                    if (stringContainsNumber !== "") {
                        let numberIndex = _option.productsList[index].indexOf(
                            stringContainsNumber);
                        if (_option.wordSub * (j + 1) > numberIndex) {
                            if (_option.productsList[index].substring(_option.wordSub * j,
                                    numberIndex)) {
                                context.fillText(_option.productsList[index].substring(_option.wordSub * j,
                                        numberIndex), -context.measureText(
                                        _option.productsList[index]
                                        .substring(_option.wordSub * j, numberIndex)).width /
                                    2,
                                    lineHeight *
                                    (j + 1));
                                context.fillText(_option.productsList[index].substring(numberIndex), -context.measureText(
                                        _option.productsList[index]
                                        .substring(numberIndex)).width / 2,
                                    lineHeight *
                                    (j + 2));
                                break;
                            } else {
                                context.fillText(_option.productsList[index].substring(numberIndex), -context.measureText(
                                        _option.productsList[index]
                                        .substring(numberIndex)).width / 2,
                                    lineHeight *
                                    (j + 1));
                                break;
                            }

                        };
                    }
                    context.fillText(_option.productsList[index].substring(_option.wordSub * j, _option.wordSub *
                            (
                                j + 1)), -context.measureText(
                            _option.productsList[index]
                            .substring(_option.wordSub * j, _option.wordSub * (j + 1))).width / 2,
                        lineHeight *
                        (j + 1));
                } else {
                    if (stringContainsNumber !== "") {
                        let numberIndex = _option.productsList[index].indexOf(
                            stringContainsNumber);
                        if (_option.wordSub * (j + 1) > numberIndex) {
                            if (_option.productsList[index].substring(_option.wordSub * j,
                                    numberIndex)) {
                                context.fillText(_option.productsList[index].substring(_option.wordSub * j,
                                        numberIndex), -context.measureText(
                                        _option.productsList[index]
                                        .substring(_option.wordSub * j, numberIndex)).width /
                                    2,
                                    lineHeight2 *
                                    (j) + lineHeight);
                                context.fillText(_option.productsList[index].substring(numberIndex), -context.measureText(
                                        _option.productsList[index]
                                        .substring(numberIndex)).width / 2,
                                    lineHeight2 *
                                    (j + 1) + lineHeight);
                                break;
                            } else {
                                context.fillText(_option.productsList[index].substring(numberIndex), -context.measureText(
                                        _option.productsList[index]
                                        .substring(numberIndex)).width / 2,
                                    lineHeight2 *
                                    (j) + lineHeight);
                                break;
                            }

                        };
                    }
                    context.fillText(_option.productsList[index].substring(_option.wordSub * j, _option.wordSub *
                            (
                                j + 1)), -context.measureText(
                            _option.productsList[index]
                            .substring(_option.wordSub * j, _option.wordSub * (j + 1))).width / 2,
                        lineHeight2 *
                        (j) + lineHeight);
                }

            }

            context.restore();
        }
        //绘制点击按钮F
        drawClick(_option);
    } //end

    //绘制点击按钮
    function drawClick(_option) {
        _option.context.drawImage(_option.cacheImg, 0 - _option.cacheImg.width / 2, 0 - (_option.cacheImg
            .height /
            2) - ((1 - (_option.cacheImg.width / _option.cacheImg.height)) * _option.cacheImg.height /
            2));
        //老方案 误删
        // let img = new Image();
        // img.onload = function () {
        //     //此段 代码算出 图片得宽高比 使展示得图品等比例 然后绝对居中
        //     _option.context.drawImage(img, 0 - (_option.canvasH / 3.2 * (img.width / img.height)) / 2,
        //         0 - _option.canvasH / 6.4 - (1 - (img.width / img.height)) * _option.canvasH / 6.4,
        //         _option.canvasH / 3.2 * (img.width / img.height),
        //         _option.canvasH / 3.2);
        //     //在click时间加载后注册
        //registeClick(_option);
        // }
        // img.src = _option.clickImg;
    } //end

    //注册点击事件 判断 触发事件的位置在不在clickImg的坐标范围内(最后写)
    //点击后先执行ajax 获取 中奖得数组 然后根据数据转动到中奖位置 (先用固定代替)
    function registeClick(_option) {
        let position_minX = parseInt(_option.canvasW) / 2 - _option.cacheImg.width / 2;
        let position_maxX = parseInt(_option.canvasW) / 2 + _option.cacheImg.width / 2;
        let position_minH = parseInt(_option.canvasH) / 2 - _option.cacheImg.height / 2;
        let position_maxH = parseInt(_option.canvasH) / 2 + _option.cacheImg.height / 2;
        _option.canvas.onclick = function (e) {
            console.log(_option.onlyone);
            //判断鼠标点击是否在范围点击抽奖范围内, 
            if ((e.offsetX >= position_minX && e.offsetX <= position_maxX) && (e.offsetY >= position_minH &&
                    e.offsetY <= position_maxH)) {
                //判断当前旋转是否结束 
                if (_option.onlyone === 0) {
                    _option.onlyone = 1;

                    //执行接口 每次点击需要获取得数据 判断domain地址是否为空 为空(测试)给个假得
                    if (_option.domain !== undefined && _option !== "" && _option !== null) {
                        //同时执行error跟success什么鬼.
                        $.ajax({
                            type: 'POST',
                            url: _option.domain,
                            data: {},
                            async: false, //由于ajax异步会限制性 ajax后面的代码 所以 我改为同步执行 ajax只要有问题后面代码就不执行
                            dataType: 'json',
                            // jsonpCallback: "showData",
                            // crossDomain: true,
                            success: function (result) {
                                if (result === null) {
                                    console.log("如果你发现在执行error方法后又进到这里了,他跟jquery_code文件冲突了")
                                    console.log("result返回结果为空 如果依然想要旋转 请注释 280 281 并修改31行 result = 1");
                                    _option.result = "error"; //测试得时候可以去除
                                    _option.onlyone = 0; //测试得时候可以去除
                                    _option.luckPosition = 1;
                                } else {
                                    if (result.error == 1) {
                                        console.log("ajax返回error结果错误 如果依然想要旋转 请注释 289 290 并修改31行 result = 1");
                                        _option.onlyone = 0; //测试得时候去掉
                                        _option.result = "error"; //测试得时候可以去除
                                    } else if (result.error == 0) {
                                        //设置成功方法所需要的数据
                                        let lucy = result.name;
                                        let position = _option.productsList.indexOf(lucy);
                                        _option.luckPosition = position; //获奖位置
                                        _option.result = result; //保存result结果
                                        _option.frequency = result.frequency //更新总抽奖次数
                                    }
                                }
                            },
                            error: function () {
                                console.log("ajax返回错误,如果你依然想要他旋转请注释 位置 303 304 并修改31行 result = 1")
                                _option.onlyone = 0;
                                _option.result = "error"; //测试得时候可以去除

                            }
                        });
                    } else {
                        console.log("你并没有写domain,如果你依然想要他旋转请注释 位置 311 312 并修改31行 result = 1")
                        _option.luckPosition = 2;
                        // _option.onlyone = 0; //测试去掉
                        // _option.result = "error"; //测试得时候可以去除
                    }
                    console.log("转盘position =========== " + _option.luckPosition);

                    //判断总次数 如果大于抽奖次数则不旋转(抽奖总次数每次掉ajax都会更新)      
                    if (_option.flagTimes < _option.frequency && _option.result !== "error" && _option.result !== "") {
                        _option.flagTimes++;
                        _option.timer = setInterval(function () {
                            

                            //变速改
                            if (turnSpeed(_option) === 0) {
                                clearPromise(_option).then(function (result) {
                                    //回复角度
                                    _option.roateRadian = 0;
                                    //延迟0.8秒 执行获奖方法
                                    setTimeout(() => {
                                        console.log(result);
                                        if (_option.success) {
                                            // _option.result = {
                                            //     "error": 0,
                                            //     "msg": null,
                                            //     "type": "2",
                                            //     "name": "5元优惠券",
                                            //     "score": "100",
                                            //     "hc_id": "00000007499328"
                                            // }
                                            _option.success(_option.result);                                        
                                        } else {
                                            console.log("如果需要回调,清添加success");                                         
                                        }
                                        _option.onlyone = 0;
                                        
                                    }, 700);
                                }).catch(function () {
                                    console.log("错误了")
                                });
                                return; //如果不加return 清除完之后还会执行几次!!!
                            }
                            _option.roateRadian += turnSpeed(_option);
                            drawProducts(_option);
                        }, 1) //setinterval end
                    } else {
                        return false;
                    } // 旋转 end
                } //onlyone end
            } //判断点击范围
        } //onclick end
    } // end

    //用来清除interval
    function clearPromise(_option) {
        clearInterval(_option.timer);
        return new Promise(function (resolve, reject) {
            resolve('OK');
        });
    } //end
    //旋转速度
    function turnSpeed(_option) {
        // _option.roateRadian * Math.PI / 180 >= Math.PI * 2 * 3 -
        //     Math.PI / 2 -
        //     _option.baseRadian / 2 - _option.baseRadian *
        //     _option.luckPosition
        //((旋转弧度与 旋转圈数-90弧度-默认弧度得一半-产品位置)*10)向下去整 在用10-这个比 等于radian需要加得度数
        var pp = Math.floor(((_option.roateRadian * Math.PI / 180) / (Math.PI * 2 * 5 - Math.PI / 2 - _option.baseRadian / 2 -
            _option.baseRadian * _option.luckPosition)) * 10);
            // console.log(pp);
        return 10-pp;
    }

}

    

3.github地址  https://github.com/a578024797/lucky   finally文件(老版本)

                       https://github.com/a578024797/qlcs_wechat_h5       magpieFestivalExtract.html(新版本)

4.转盘本身逻辑存在问题,并未考虑性能,有时间会逐步完善.(目前待解决 旋转渲染卡顿 微信开发者卡顿但pc 手机没问题 , 变速不流畅);

5. 修改信息

    2018/7/31  增加 ajax错误时候的判断(跟条形码jq冲突但不影响),修改变速(依旧稍微卡顿 用的setinterval),

    2018/8/3   修改一些逻辑,删除一些没必要的属性,修复一些根据逻辑得错误,具体代码更到git .

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值