有关遮罩和图层叠加的问题(附刮刮卡demo)

  • 内容
  • 评论
  • 相关

最近有童鞋问了在mask下graphics很多方面的bug,我今天就来一一解决一下。

首先先做个连续画图。

 stage.addEventListener("stagemousedown",function (event){
        shape.graphics.beginFill("#ff0000")
        shape.graphics.drawCircle(event.rawX,event.rawY,10);
        shape.graphics.endFill();
    });
    shape = new createjs.Shape();
    container.addChild(shape);

http://www.ajexoop.com/test/graphics/test1.html

可以画出来,然后我们把它当做遮罩以后呢?

 stage.addEventListener("stagemousedown",function (event){
        shape.graphics.beginFill("#ff0000")
        shape.graphics.drawCircle(event.rawX,event.rawY,10);
        shape.graphics.endFill();
    });
    var bitmap = new createjs.Bitmap(images.back);
    container.addChild(bitmap);
    shape = new createjs.Shape();
    bitmap.mask = shape;

http://www.ajexoop.com/test/graphics/test2.html

显然只能显示最后一次画的了。

要全部显示其实也很简单,只用一次beginFill不要endFill就可以了

 stage.addEventListener("stagemousedown",function (event){
        shape.graphics.drawCircle(event.rawX,event.rawY,10);
    });
    var bitmap = new createjs.Bitmap(images.back);
    stage.addChild(bitmap);
    shape = new createjs.Shape();
    shape.graphics.beginFill("#ff0000")
    bitmap.mask = shape;

http://www.ajexoop.com/test/graphics/test3.html

大家可以看到,修改了以后bug更夸张了,东西全部连一块了,关于这个bug一开始本人以为是mask的问题,但是去掉mask也一样。然后我拿flash试了一下,结果2者机制并不一样,flash可以随时endFill()不需要一直连续的draw。

//as3 flash代码
var shape:Shape = new Shape();
shape.graphics.beginFill(0xff0000)
shape.graphics.drawRect(0,0,100,100)
shape.graphics.endFill();
mc.mask = shape
stage.addChild(shape)

stage.addEventListener(MouseEvent.CLICK,clickHandler);
function clickHandler(e:MouseEvent):void
{
	shape.graphics.beginFill(0xff0000)
	shape.graphics.drawCircle(stage.mouseX,stage.mouseY,10);
	shape.graphics.endFill();
	trace("ok")
}

我先去查了下api,发现了closePath(闭合路径),确实这问题像是PS里用钢笔工具时没闭合路径,加上closePath试一下。

  stage.addEventListener("stagemousedown",function (event){
         shape.graphics.drawCircle(event.rawX,event.rawY,10).closePath();
    });
    var bitmap = new createjs.Bitmap(images.back);
    stage.addChild(bitmap);
    shape = new createjs.Shape();
    shape.graphics.beginFill("#ff0000")
    bitmap.mask = shape;

http://www.ajexoop.com/test/graphics/test4.html

结果一次就成功。但是!同一个圆点双击后又有bug了(天杀的createjs团队,bug真多)

QQ图片20190125160813.png

每次点击都会向第一次花的发现靠拢,怎么解决呢,那我每次点的时候重新空画一下不就好了(试了好多种方法只有这个管用),上代码:

stage.addEventListener("stagemousedown",function (event){
    shape.graphics.drawCircle(event.rawX,event.rawY,10).closePath();
    shape.graphics.drawCircle(0,0,0).closePath();
});
var bitmap = new createjs.Bitmap(images.back);
stage.addChild(bitmap);
shape = new createjs.Shape();
shape.graphics.beginFill("#ff0000");
bitmap.mask = shape;

可以看到我drawCircle(0,0,0)空画了一下,测试一下这次确实不会有问题了。

http://www.ajexoop.com/test/graphics/test5.html?v=0.0.1

接下来分享出刮刮卡的demo

http://www.ajexoop.com/test/graphics/guaguale.html?v=0.0.1

代码在这里:

//main.js
var canvas,stage,images = {},txt,shape,maskTxtShape,eraseShape,container;

function init() {
    canvas = document.getElementById("mainView");
    stage = new createjs.Stage(canvas);//字容器
    container = new createjs.Container();
    createjs.Touch.enable(stage);

    stage.addEventListener("stagemousedown",startMove);
    stage.addEventListener("stagemouseup",endMove);

    txt = new createjs.Text();//字
    txt.text = "一等奖";
    txt.font = "bold 36px Arial";
    container.addChild(txt)

    maskTxtShape = new createjs.Shape();//字遮罩
    maskTxtShape.graphics.beginFill("#ff0000");
    maskTxtShape.graphics.drawCircle(0,0,0);//什么都没有是无法遮罩的
    container.mask = maskTxtShape;

    shape = new createjs.Shape();//灰色图层
    shape.graphics.beginFill("#999999");
    shape.graphics.drawRect(0,0,120,50);
    shape.graphics.endFill();
    stage.addChild(shape);


    eraseShape = new createjs.Shape();//擦除图层
    eraseShape.graphics.beginFill("rgba(255,255,255,1)");
    eraseShape.compositeOperation = "destination-out";//叠加方式
    stage.addChild(eraseShape);

    stage.addChild(container);//是字放在最上面而不是擦除图层,如果擦除图层在最上面会把一切都擦掉


    createjs.Ticker.setFPS(30);
    createjs.Ticker.addEventListener("tick", stageBreakHandler);

}
function startMove(event)
{
    moveHandler(event)
    stage.addEventListener("stagemousemove",moveHandler)
}
function endMove(event)
{
    stage.removeEventListener("stagemousemove",moveHandler)
}
function moveHandler(event)
{
    maskTxtShape.graphics.drawCircle(event.rawX,event.rawY,10).closePath();

    eraseShape.graphics.drawCircle(event.rawX,event.rawY,10).closePath();
}
function stageBreakHandler(event)
{
    stage.update();
}

这个刮刮乐的项目,比我想象的麻烦,主要createjs的叠加方式的api居然直接用原生的(createjs的compositeOperation同原生的globalcompositeoperation),让我找了半天,还有图层关系也是颠倒的,需要好好理解一下。

最近据群友提出,这个demo还是有个小问题,就是快速刮开的时候会不连续,就像下面那样:

QQ图片20190125174513.png

大家看,4个洞不连续,很多懂的人看代码会说用lineTo也就是画线代替drawCircle画圆就可以了,虽然是这么说也对,但是问题在于createjs的mask是无法支持矢量线遮罩的,所以要想连续只能在2次mousemove之间手动画圆补全路径,那有没有不需要通过数学补全去做的方法呢?也有!但是做法也不轻松,而且相对比较耗性能,上代码:

//main.js
var canvas,stage,images = {},txt,shape,maskTxtShape,eraseShape,txtContainer,eraseContainer;

function init() {
    canvas = document.getElementById("mainView");
    stage = new createjs.Stage(canvas);//字容器
    txtContainer = new createjs.Container();
    eraseContainer = new createjs.Container();
    createjs.Touch.enable(stage);

    stage.addEventListener("stagemousedown",startMove);
    stage.addEventListener("stagemouseup",endMove);

    txt = new createjs.Text();//字
    txt.text = "一等奖";
    txt.font = "bold 36px Arial";
    txtContainer.addChild(txt)

    maskTxtShape = new createjs.Shape();//字遮罩叠加
    maskTxtShape.graphics.beginFill("#ffff00");
    maskTxtShape.graphics.drawCircle(0,0,0);//什么都没有是无法遮罩和叠加的
    maskTxtShape.graphics.setStrokeStyle(10,"round").beginStroke("#FF0");
    maskTxtShape.compositeOperation = "destination-in";//叠加方式
    // txtContainer.mask = maskTxtShape;//mask无法完成对线的遮罩
    maskTxtShape.cache(0,0,120,50);
    txtContainer.addChild(maskTxtShape);
    txtContainer.cache(0,0,120,50)



    shape = new createjs.Shape();//灰色图层
    shape.graphics.beginFill("#999999");
    shape.graphics.drawRect(0,0,120,50);
    shape.graphics.endFill();
    eraseContainer.addChild(shape);


    eraseShape = new createjs.Shape();//擦除图层
    eraseShape.graphics.setStrokeStyle(10,"round").beginStroke("#FF0");
    eraseShape.compositeOperation = "destination-out";//叠加方式
    eraseContainer.addChild(eraseShape);

    stage.addChild(eraseContainer);
    stage.addChild(txtContainer);//是字放在最上面而不是擦除图层,如果擦除图层在最上面会把一切都擦掉

    createjs.Ticker.setFPS(30);
    createjs.Ticker.addEventListener("tick", stageBreakHandler);

}
var lostPoint = new createjs.Point()
function startMove(event)
{
    lostPoint.x = event.rawX;
    lostPoint.y = event.rawY;
    moveHandler(event)
    stage.addEventListener("stagemousemove",moveHandler)
}
function endMove(event)
{
    stage.removeEventListener("stagemousemove",moveHandler)
}
function moveHandler(event)
{
    maskTxtShape.graphics.setStrokeStyle(10,"round").beginStroke("#FF0");
    maskTxtShape.graphics.moveTo(lostPoint.x,lostPoint.y)
    maskTxtShape.graphics.lineTo(event.rawX,event.rawY)
    eraseShape.graphics.setStrokeStyle(10,"round").beginStroke("#FF0");
    eraseShape.graphics.moveTo(lostPoint.x,lostPoint.y)
    eraseShape.graphics.lineTo(event.rawX,event.rawY)
    lostPoint.x = event.rawX;
    lostPoint.y = event.rawY;
    maskTxtShape.updateCache();
    txtContainer.updateCache();
    // txtContainer.mask = maskTxtShape;
}
function stageBreakHandler(event)
{
    stage.update();
}

测试地址:http://www.ajexoop.com/test/graphics/guaguale2.html

大家看上面的代码和demo,我所有画圆的逻辑都变成了画线,但是除此之外还有二个关键操作,一个是加了2个cache,一个是去掉了mask换成了叠加方式

maskTxtShape.cache(0,0,120,50);

txtContainer.cache(0,0,120,50);

为什么要用cache和叠加方式呢?因为cache了就会把线变成普通的矢量对象,就可以进行叠加方式,但是用遮罩就算用cache也不能生效(亲测)

那为什么要2次cache呢?第一次cache是转线段为普通对象,第二次cache是防止二次叠加方式冲突,没错这是个重要的知识点,同一个位置超过2个叠加方式会冲突,cache后可以解决冲突。

大家还可以看到,每次mousemove都需要2次cache,这样性能会多消耗不少,虽然说cache的面积比较小,不会消耗太多,所以还是推荐大家手动补全圆的路径。

最后放一张叠加关系图:

评论

1条评论

发表评论

电子邮件地址不会被公开。