Canvas 在屏幕上以用户 drag/pan 的身份清除

Canvas clear as user drag/pan on screen

我遇到了一个我真的不知道从哪里开始解决的问题,所以我希望这个问题不要太宽泛。

我正在制作并在屏幕上应用它,我将有一个包含一些信息的矩形(假设它是一个 <ion-grid>),我需要用另一个矩形覆盖那个矩形,所以,作为用户在屏幕上滑动手指,它会显示后面的内容。

想到的第一个解决方案是使用 canvas,并且我看到了一些在单击时从 canvas 矩形中删除圆的示例。情况是我对 canvas 一无所知,而且我没有可分享的代码,我会尝试一些然后编辑我的问题。

第二种解决方案是(丑陋的)解决方法,类似于:

<ion-grid>
  <ion-row>
    <ion-col width-5 (pan)='somethingToHideTheCol()'></ion-col> //width-5 or less
  </ion-row>
</ion-grid>

因此,当 <ion-col> 被平移时,它会调用一个函数来隐藏它或将背景更改为透明。

第二种解决方案不会模仿用户的手指形状,它会更难看,但也许可行。第一个 canvas 的解决方案会更好,但我不知道从哪里开始,所以:

任何想法、代码、link、教程都是有帮助的。谢谢:)

编辑

这是我要创建的效果:

因此,当用户在屏幕上触摸并拖动手指时,它会删除 canvas 的一部分。

Canvas 确实是要走的路:) 您所要做的就是在您的元素上放置一个 canvas,填充 canvas 的背景,然后使用 CompositeOperations 移除部分背景:

var CanvasHide = function(settings) {
  this.mouseDown = false;
 this.el = document.querySelectorAll("*[data-canvas-hide]");
 this.init = function() {
   var self = this;
   for (var i=0, l=this.el.length; i<l; i++) {
     self.addCanvas(this.el[i]);
    }
  }
  
  this.addCanvas = function(el) {
    var self = this;
   var canvas = document.createElement("canvas");
    canvas.width = el.offsetWidth;
    canvas.height = el.offsetHeight;
    el.appendChild(canvas);
    ctx = canvas.getContext("2d");
    ctx.fillStyle = "#666";
    ctx.fillRect(0,0,canvas.width, canvas.height);
    canvas.onmousedown=function(){self.mouseDown=true;};
    canvas.onmouseup=function(){self.mouseDown=false;};
    canvas.onmousemove = function(e) {
      if (self.mouseDown) {
        var x = e.pageX;
        var y = e.pageY;
        ctx.fillStyle = "#fff";
     ctx.globalCompositeOperation = 'destination-out'
     ctx.beginPath();
        ctx.arc(x - settings.radius/2, y - settings.radius/2, settings.radius, 0, 2 * Math.PI, false);
        ctx.fill();
      }
    }
  }
  
  this.init();
}

var canvasHide = new CanvasHide({radius:20});
*[data-canvas-hide] {
  position: relative;
}

canvas {
  position: absolute;
  top: 0;
  left: 0;
}
<div class="container" data-canvas-hide>
<h1>
Title
</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. In commodi odio, incidunt saepe aperiam nam rerum at ex consequatur tempora quae temporibus. Odio, facere repudiandae suscipit doloribus autem unde tempore qui earum illum, minima consequatur officia repellat labore, doloremque eveniet amet eligendi quam aspernatur quisquam inventore pariatur temporibus. Ducimus adipisci numquam quaerat. Atque doloremque nostrum blanditiis dicta repellendus, provident, dolorum modi praesentium perferendis animi ab. Eaque totam itaque quo provident ea sint explicabo dicta adipisci doloremque ipsam, voluptate. Quisquam maxime ipsa fugit magnam tempora, exercitationem minus. Amet doloremque vero consequuntur perferendis voluptate perspiciatis eaque, assumenda inventore, quibusdam molestias obcaecati quis.</p>
</div>

编辑:要使其在触摸设备上运行,您需要将 mousevents 更改为 touchevents。

canvas几乎总是制作任何形式的图形作品的最佳方式。

接缝我写得不够快,无法得到答案。好吧,不要浪费代码,这里是 canvas 的另一个版本。

我使用 requestAnimationFrame 来更新 canvas 因为鼠标可以以非常高的速率发射。从 IO 事件渲染从来都不是一个好主意,鼠标事件更是如此。

还包括简单的触摸界面和一些扰乱 canvas 绘制方式的方法,以产生不同的 FX 而不仅仅是擦除。最终,这最好在 WebGL 和一个着色器中完成,该着色器模拟划痕显示表面、方向传感器等,以获得额外的漫反射镜面反射着色 FX,从而为界面提供非常高质量的感觉。

var divBounds = textDiv.getBoundingClientRect();
// create two so we can add some FX
var canvasA = document.createElement("canvas");  // this one is for display
var canvasB = document.createElement("canvas");
canvasB.width = canvasA.width = divBounds.width;
canvasB.height = canvasA.height = divBounds.height;
canvasA.style.position = "absolute";
canvasA.style.top = divBounds.top + "px"
canvasA.style.left = divBounds.left + "px"
var ctxA = canvasA.getContext("2d");
var ctxB = canvasB.getContext("2d");
document.body.appendChild(canvasA);


//--------------------------------------------------------------------
// Mouse and touch interface
//---------------------------------------------------------------------
const input = (function(){
    var deviceIO;
    // use navigator.maxTouchPoints to discover if device has touch caps.
    if(navigator.maxTouchPoints > 0){
        deviceIO = function(element){
            const canvasPixelScale = 1; // not used
            var top = 0;  // offset to element
            var left = 0; 
            var touch = {
                x : 0,  // primary touch point
                y : 0,
                points : [], // all touch points
                count : 0,  // count of active touches.
                isTouched : false, // true if touched
                events : "touchstart,touchmove,touchend,touchcancel".split(","),
            }
            // aliases for the lazy programmer
            var t = touch;
            var TP = touch.points;
            
            
            // functions to track touch points
            // Rather than create new touch points all the time this code uses a pre allocated array of touch points
            // to track individual touch points.            
            function newTouch(){ // returns a new (empty) touch
                for(var j = 0; j < touch.pCount; j ++){if(TP[j].id === -1){return TP[j]}}
            }
            function getTouch(id){ // returns a touch by its id
                for(var j = 0; j < touch.pCount; j ++) {if (TP[j].id === id) {return TP[j]}}            
            }
            // sets the coordinates of a touch point
            function setTouch(touchPoint, touchItem, ending){
                if(!ending){
                    touchPoint.dx = touchItem.pageX / canvasPixelScale - touchPoint.x;
                    touchPoint.dy = touchItem.pageY / canvasPixelScale - touchPoint.y;
                }
                touchPoint.x = (touchItem.pageX / canvasPixelScale) - left;
                touchPoint.y = (touchItem.pageY / canvasPixelScale) - top;        
            }
            // handle all touch events
            function touchEvent(event){
                event.preventDefault();    
                var tp;
                var e = event;
                var cT = event.changedTouches;
                // ensure page offset is correct by updating it each event
                var bounds = element.getBoundingClientRect()
                left = bounds.left + scrollX; 
                top = bounds.top + scrollY; 
                
                if(event.type === "touchstart"){
                    for(var i = 0; i < cT.length; i ++){
                        var tp = newTouch();
                        setTouch(tp,cT[i]);
                        tp.dx = 0; 
                        tp.dy = 0; 
                        tp.id = cT[i].identifier;
                    }
                }else if(event.type === "touchmove"){
                    for(var i = 0; i < cT.length; i ++){
                        setTouch(getTouch(cT[i].identifier),cT[i]);
                    }
                }else if(event.type === "touchend"){
                    for(var i = 0; i < cT.length; i ++){
                        setTouch(tp = getTouch(cT[i].identifier),cT[i],true);
                        tp.id = -1;
                    }
                }else if(event.type === "touchcancel"){
                    for(var i = 0; i < cT.length; i ++){
                        var tp = getTouch(cT[i].identifier);
                        tp.id = -1;
                    }
                }
                //check for any active touch events. If none turn off touch flag
                touch.isTouched = false;
                touch.count = 0;
                for(var j = 0; j < touch.pCount; j ++) {
                    if (TP[j].id !== -1) {            
                        if(touch.count === 0){  // use the first touch point as main input coord
                            touch.x = TP[j].x;
                            touch.y = TP[j].y;
                        }
                        touch.isTouched = true;
                        touch.count += 1;
                    }
                }
                return false;
            }
            touch.pCount = navigator.maxTouchPoints;
            for(var i = 0; i < touch.pCount; i ++){// create a set of touch points that persist 
                touch.points[i] = { x : 0,y : 0,dx : 0,dy : 0,down : false,id : -1,}                
            }
            touch.events.forEach(n => { element.addEventListener(n, touchEvent); } );        
            return touch;
        }
    }else{  // use the mouse instead
        deviceIO = function(element){
            function preventDefault(e) { e.preventDefault(); }
            var i;
            var mouse = {
                x : 0, y : 0, buttonRaw : 0,
                over : false,  // mouse is over the element
                bm : [1, 2, 4, 6, 5, 3], // masks for setting and clearing button raw bits;
                mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover".split(",")
            };
            function mouseMove(e) {
                var t = e.type;
                var m = mouse;
                var bounds = element.getBoundingClientRect()
                m.x = e.pageX - bounds.left - scrollX; 
                m.y = e.pageY - bounds.top - scrollY; 
                if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1];
                } else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2];
                } else if (t === "mouseout") { m.buttonRaw = 0; m.over = false;
                } else if (t === "mouseover") { m.over = true; }
                e.preventDefault();
            }
            mouse.mouseEvents.forEach(n => { element.addEventListener(n, mouseMove); } );

            return mouse;
        }
    }  
    return deviceIO;
}());

const io = input(canvasA); // start user input

//------------------------------------------------------------------
// Scratchy
//------------------------------------------------------------------
const message = "Touch!"; // message on canvas
const font = "68px arial black"; // text for message
const fontCol = "#777"; // colour off message
// the display settings for app
const drawWidth = 40; // radius of touch area
const edgeStyle = "#777"; // colour of overlay
const edgeWidth = 4; // make look nice
const overlayStyle = "#999"; // colour of overlay
const displayAccentMain = "rgba(80,160,255,1)"; // make look nice
const displayAccent = "rgba(60,60,60,1)";
const touchPointFalloffA = 1.1; // shift center of s curve. Less than one and like touch press harder more than one and make touch more like feather scratching glass
const touchPointFalloffB = 2; // strength off s curve
var lastX;
var lastY; // save the position of last touch mouse
const touchStyle = createTouchStyle(touchPointFalloffA, touchPointFalloffB); // creates a gradient used to draw the touch point
start(); // start the app;
function createTouchStyle(a,b){
    const touchGradient = ctxA.createRadialGradient(0,0,0,0,0,drawWidth);
    const curveS = (x,p) => {
       x = x < 0 ? 0 : x > 1 ? 1 : x;
       var xx = Math.pow(x,p);
       return xx / (xx + Math.pow(1-x,p));
    };
    const curveB = (x,p) => {
       x = x < 0 ? 0 : x > 1 ? 1 : x;
       return Math.pow(x,p);
    };
    for(var i = 0; i <= 1; i += 0.025){
        touchGradient.addColorStop(i,"rgba(0,0,0,"+curveS(curveB(1-i,a),b)+")");
    }
    return touchGradient;
}

// Draw the starting canvas
function start() {
    ctxB.font = font;
    ctxB.textAlign = "center";
    ctxB.textBaseline = "middle";
    ctxB.fillStyle = edgeStyle;
    ctxB.fillRect(0, 0, canvasA.width, canvasA.height);
    ctxB.fillStyle = overlayStyle;
    ctxB.fillRect(edgeWidth, edgeWidth, canvasA.width - edgeWidth * 2, canvasA.height - edgeWidth * 2);    
    ctxB.fillStyle = fontCol;
    ctxB.fillText(message,canvasB.width / 2, canvasB. height / 2);    
    ctxA.drawImage(canvasB, 0, 0); // put canvas on display
    textDiv.className = "showText"; // set color of text so that it can be read
    update();
}


// update the canvas when there is touch or mouse changes
function update() {
    // only on touch or mouse button down
    if (io.isTouched || io.buttonRaw === 1) {
        if(lastX === undefined){ // if start of touch set last to current
            lastX = io.x;
            lastY = io.y;
        }
        // set gradient and copmosite mode
        ctxB.fillStyle = touchStyle;
        ctxB.globalCompositeOperation = "destination-out";

        // If large movement then smear the couch over the distance
        // Find the distance from last contact point / mouse down
        var dx = io.x - lastX;
        var dy = io.y - lastY;
        var dist = Math.sqrt(dx*dx+dy*dy);
        if(dist > 1.5){  // only smear out contact if dist over 1.5 pixels
            lastX += (dx /= dist);  // normalise vector between contacts
            lastY += (dy /= dist);  // also step past last pos as that has been drawn
            dist += 1;
            ctxB.globalAlpha = Math.max(0.05,1 / dist); // reduce FX depending on dist
            while(dist > 0){ // move from last to current
                ctxB.beginPath();
                ctxB.setTransform(1,0,0,1,lastX,lastY);
                ctxB.arc(0, 0, drawWidth, 0, Math.PI * 2);
                ctxB.fill();
                lastX += dx;
                lastY += dy;
                dist -= 1;
            }
        }else{        
            // just a single touch point
            ctxB.globalAlpha = 1.0;
            ctxB.setTransform(1,0,0,1,io.x,io.y);
            ctxB.beginPath();
            ctxB.arc(0, 0, drawWidth, 0, Math.PI * 2);
            ctxB.fill();
        }
        // save this pos for the next frame
        lastX = io.x;
        lastY = io.y;
        
        // draw to display canvas using shadows to add FX

        ctxA.clearRect(0,0,canvasA.width,canvasA.height);
        // Add to display canvas with very slight highlight
        ctxA.shadowOffsetX = ctxA.shadowOffsetY = 1;
        ctxA.shadowColor = displayAccentMain;
        ctxA.drawImage(canvasB, 0, 0);

        ctxA.shadowOffsetX = ctxA.shadowOffsetY = -1;
        ctxA.shadowColor = displayAccent;
        ctxA.drawImage(canvasB, 0, 0);

    }else{
        lastX = undefined; // no contact no last contact!
    }
    requestAnimationFrame(update);
}
div {
    font-family : arial black;
    font-size : 16px;
}
#textDiv {
    width: 500px;
    height: 400px;
    padding: 4px;
    border: 4px #fff solid;
    box-shadow: 5px 5px 5px rgba(0,0,0,0.5);   
}  
.hideText {
    background: #999;
    color: #999;
}
.showText {  /* because the canvas is added after the div is displayed */
         /* the above style hides the text and this make is visible */
         /* when canvas is ready */
   background: #000;
   color: #fff;
 

}
<div>
    <h2>Test touch</h2>    
    <div id="textDiv" class="hideText">
        <h3>From MDN TouchEvent</h3>
        <p>The TouchEvent interface represents an event sent when the state of contacts with a touch-sensitive surface changes. This surface can be a touch screen or trackpad, for example. The event can describe one or more points of contact with the screen and includes support for detecting movement, addition and removal of contact points, and so forth.</p>
        <p>Touches are represented by the Touch object; each touch is described by a position, size and shape, amount of pressure, and target element. Lists of touches are represented by TouchList objects</p>
    </div>
</div>