Html 垂直和水平滚动的 Html5 画布

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/36219632/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-29 13:03:52  来源:igfitidea点击:

Html5 canvas scrolling vertically and horizontally

htmlhtml5-canvas

提问by Vineet

<!DOCTYPE html>
<html>
<head>
    <style type="text/css">
        #canvasOne
        {
            border: 1px solid black;            
        }
    </style>
    <script src="http://code.jquery.com/jquery-1.10.2.js" type="text/javascript"></script>
</head>
<body>
    <div align="center">
        <canvas id="canvasOne">
        </canvas>
    </div>

    <script type="text/javascript">

        var myCanvas = document.getElementById("canvasOne");
        var myContext = myCanvas.getContext("2d");

        var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;

        init();

        var numShapes;
        var shapes;
        var dragIndex;
        var dragging;
        var mouseX;
        var mouseY;
        var dragHoldX;
        var dragHoldY;
        var timer;
        var targetX;
        var targetY;
        var easeAmount;
        var bgColor;
        var nodes;
        var colorArr;

        function init()
        {
            myCanvas.width = $(window).width() - 200;
            myCanvas.height = $(window).height() - 200;

            shapes = [];
            nodes = ["0;Person;24828760;Alok Kumar;Gorakhpur;#F44336;28",
                     "0;Suspect;04/Dec/2016;4;Suman_Biswas;#3F51B5;20","1;Rule;4;Apparent Means;3 Parameter;#EEFF41;20",
                     "0;Policy;36QA649749;In-Force;Quarterly;#FF9800;20","3;Product;Pension;Saral Pension;SRPEN;#795548;20","3;Payment;Cheque;Realized;Lucknow;#0091EA;20",
                     "0;Policy;162348873;Lapsed;Quarterly;#FF9800;20","6;Product;Pension;Life-Long Pension;LLPP;#795548;20","6;Payment;Cheque;Realized;Gorakhpur;#0091EA;20",
                     "0;Policy;1EQF178639;Lapsed;Monthly;#FF9800;20","9;Product;Life;Shield;SHIELDA;#795548;20","9;Payment;Demand Draft;Realized;Lucknow;#0091EA;20"];                                          

            numShapes = nodes.length;

            makeShapes();

            drawScreen();       

            myCanvas.addEventListener("mousedown", mouseDownListener, false);
        }

        //drawing
        function makeShapes()
        {
            var tempX;
            var tempY;

            for(var i = 0; i < numShapes; i++)
            {                                   
                var centerX = myCanvas.width/2;
                var centerY = myCanvas.height/2;

                var nodeColor = nodes[i].split(";")[5];

                var nodeRadius = nodes[i].split(";")[6];

                var nodeConnect = nodes[i].split(";")[0];

                if(i == 0)//center of circle
                {                   
                    tempX = centerX
                    tempY = centerY;                    
                }
                else
                {
                    //tempX = Math.random() * (myCanvas.width - tempRadius);
                    //tempY = Math.random() * (myCanvas.height - tempRadius);

                    //var x = x0 + r * Math.cos(2 * Math.PI * i / items);
                    //var y = y0 + r * Math.sin(2 * Math.PI * i / items); 


                    //250 is the distance from center node to outside nodes it can be actual radius in degrees
                    tempX = shapes[nodeConnect].x + 300 * Math.cos(2 * Math.PI * i / numShapes);
                    tempY = shapes[nodeConnect].y + 300 * Math.sin(2 * Math.PI * i / numShapes);                                    
                }

                tempShape = {x: tempX, y: tempY, rad: nodeRadius, color: nodeColor, text: nodes[i]};

                shapes.push(tempShape);
            }       
        }

        //drawing both shape (line and circle) and screen

        function drawScreen()
        {
            myContext.fillStyle = "#ffffff";
            myContext.fillRect(0, 0, myCanvas.width, myCanvas.height);
            drawShapes();
        }

        function drawShapes()
        {       
            //line
            for(var i = 1; i < numShapes; i++)
            {
                myContext.beginPath();
                myContext.strokeStyle = "#B2B19D";

                var nodeConnect = nodes[i].split(";")[0];

                myContext.moveTo(shapes[nodeConnect].x, shapes[nodeConnect].y);
                myContext.lineTo(shapes[i].x, shapes[i].y);
                myContext.stroke();
            }

            //circle        
            for(var i = 0; i < numShapes; i++)
            {                           
                myContext.fillStyle = shapes[i].color;
                myContext.beginPath();
                myContext.arc(shapes[i].x, shapes[i].y, shapes[i].rad, 0, 2*Math.PI, false);                
                myContext.closePath();
                myContext.fill();
            }

            //text
            for(var i = 0; i < numShapes; i++)
            {
                myContext.beginPath();          
                myContext.font = '10pt Arial';
                myContext.fillStyle = 'black';
                var textarr = shapes[i].text.split(";");

                myContext.fillText(textarr[1], shapes[i].x + 30, shapes[i].y - 24);
                /*myContext.fillText(textarr[2], shapes[i].x + 30, shapes[i].y + 1);
                myContext.fillText(textarr[3], shapes[i].x + 30, shapes[i].y + 22);
                myContext.fillText(textarr[4], shapes[i].x + 30, shapes[i].y + 44);*/           
                myContext.closePath();
                myContext.fill();
            }


        }

        //animation

        function mouseDownListener(evt)
        {
            var highestIndex = -1;

            var bRect = myCanvas.getBoundingClientRect();
            mouseX = (evt.clientX - bRect.left) * (myCanvas.width/bRect.width);
            mouseY = (evt.clientY - bRect.top) * (myCanvas.height/bRect.height);

            for(var i = 0; i < numShapes; i++)
            {
                if(hitTest(shapes[i], mouseX, mouseY))
                {
                    dragging = true;
                    if(i > highestIndex)
                    {
                        dragHoldX = mouseX - shapes[i].x;
                        dragHoldY = mouseY - shapes[i].y;
                        highestIndex = i;
                        dragIndex = i;
                    }               
                }
            }

            if(dragging)
            {
                window.addEventListener("mousemove", mouseMoveListener, false);
            }

            myCanvas.removeEventListener("mousedown", mouseDownListener, false);
            window.addEventListener("mouseup", mouseUpListener, false);

            if(evt.preventDefault)
            {
                evt.preventDefault;
            }

            return false;
        }

        function mouseMoveListener(evt)
        {
            var shapeRad = shapes[dragIndex].rad;

            var minX = shapeRad;
            var maxX = myCanvas.width - shapeRad;

            var minY = shapeRad;
            var maxY = myCanvas.height - shapeRad;

            //get mouse position correctly
            var bRect = myCanvas.getBoundingClientRect();
            mouseX = (evt.clientX - bRect.left)*(myCanvas.width / bRect.width);
            mouseY = (evt.clientY - bRect.top)*(myCanvas.height / bRect.height);

            //clamp x and y position to prevent object from dragging outside canvas
            posX = mouseX - dragHoldX;
            posX = (posX < minX) ? minX : ((posX > maxX) ? maxX : posX);
            posY = mouseY - dragHoldY;      
            posY = (posY < minY) ? minY : ((posY > maxY) ? maxY : posY);

            shapes[dragIndex].x = posX;
            shapes[dragIndex].y = posY;

            drawScreen();       
        }

        function mouseUpListener(evt)
        {
            myCanvas.addEventListener("mousedown", mouseDownListener, false);
            window.removeEventListener("mouseup", mouseUpListener, false);

            if(dragging)
            {
                dragging = false;                                   
                window.removeEventListener("mousemove", mouseMoveListener, false);          
            }
        }

        function hitTest(shape, mx, my)
        {
            var dx = mx - shape.x;
            var dy = my - shape.y;

            return(dx * dx + dy * dy < shape.rad * shape.rad);
        }   

    </script>
</body>
</html>
  1. The following canvas animation creates nodes and edges. However due to space constraint, some of the nodes are not visible due to canvas height and width. Even adding overflow css to canvas dosen't help as i am not able to scroll.
  1. 以下画布动画创建节点和边。但是由于空间限制,由于画布的高度和宽度,某些节点不可见。即使将溢出 css 添加到画布也无济于事,因为我无法滚动。

采纳答案by markE

So your node drawings don't fit on the canvas size?

所以你的节点图不适合画布大小?

You can easily "shrink" your content to fit the visible canvas with just 1 command!

只需 1 个命令,您就可以轻松“缩小”您的内容以适应可见的画布!

The context.scale(horizontalRescale,verticalRescale)command will shrink every following drawing by your specified horizontalRescale & verticalRescale percentages.

context.scale(horizontalRescale,verticalRescale)命令将按您指定的horizo​​ntalRescale 和verticalRescale 百分比缩小每个后续绘图。

An Important note:You must make horizontalRescale,verticalRescale the same value or your content will be distorted.

重要提示:您必须使 horizo​​ntalRescale、verticalRescale 的值相同,否则您的内容会失真。

The nice thing about using context.scaleis that you don't have to change any of the code that draws your nodes ... canvas automatically scales all those nodes for you.

使用的好处context.scale是您不必更改任何绘制节点的代码......画布会自动为您缩放所有这些节点。

For example, this code will shrink your nodes to 80% of their original size:

例如,此代码会将您的节点缩小到其原始大小的 80%:

var downscaleFactor= 0.80;

context.scale( downscaleFactor, downscaleFactor );

Rather than go through your 200+ lines of code, I leave it to you to calculate downscaleFactor.

与其遍历你的 200 多行代码,我把它留给你来计算downscaleFactor

回答by Kaiido

<canvas>context doesn't have a built-in scroll method.

<canvas>context 没有内置的滚动方法。

You then have multiple ways to circumvent this limitation.

然后,您有多种方法可以规避此限制。

The first one, is as in @markE's answer, to scale your context's matrix so that your drawings fit into the required space. You could also refactor your code so that all coordinates are relative to the canvas size.
This way, you won't need scrollbars and all your drawings will just be scaled appropriately, which is the desirable behavior in most common cases.

第一个与@markE 的答案一样,用于缩放上下文矩阵,以便您的绘图适合所需的空间。您还可以重构您的代码,以便所有坐标都相对于画布大小。
这样,您就不需要滚动条,您的所有绘图都将被适当缩放,这是大多数常见情况下的理想行为。



But if you really need to have some scrolling feature, here are some ways :

但是如果你真的需要一些滚动功能,这里有一些方法:



The easiest and most recommended one : let the browser handle it.

最简单也是最推荐的一种:让浏览器处理它

You will have to set the size of your canvas to the maximum of your drawings, and wrap it in an other element which will scroll. By setting the overflow:autocss property on the container, our scrollbars appear and we have our scrolling feature.

您必须将画布的大小设置为绘图的最大值,并将其包裹在另一个将滚动的元素中。通过overflow:auto在容器上设置css 属性,我们的滚动条就会出现,我们就有了滚动功能。

In following example, the canvas is 5000px wide and the container 200px.

在以下示例中,画布宽 5000 像素,容器宽 200 像素。

var ctx = canvas.getContext('2d');
ctx.textAlign = 'center';
for (var w = 0; w < canvas.width; w += 100) {
  for (var h = 0; h < canvas.height; h += 100) {
    ctx.fillText(w + ',' + h, w, h);
  }
}
#container {
  width: 200px;
  height: 200px;
  overflow: auto;
  border: 1px solid;
}
canvas{
  display: block;
}
<div id="container">
  <canvas id="canvas" height="5000" width="5000"></canvas>
</div>

Main advantages :

主要优势:

  • easily implemented.
  • users are used to these scrollbars.
  • 易于实施。
  • 用户习惯了这些滚动条。

Main caveats :

主要注意事项:

  • You're limited by canvas maximum sizes.
  • If your canvas is animated, you'll also draw for each frame parts of the canvas that aren't visible.
  • You have small control on scrollbars look and you'll still have to implement drag-to-scroll feature yourself for desktop browsers.
  • 您受到画布最大尺寸的限制。
  • 如果您的画布是动画的,您还将为画布的每个帧部分绘制不可见的部分。
  • 您对滚动条外观的控制很小,您仍然需要自己为桌面浏览器实现拖动滚动功能。


A second solution, is to implement this feature yourself, using canvas transform methods : particularly translate, transformand setTransform.

第二种解决方案是自己实现此功能,使用画布变换方法:特别是translatetransformsetTransform

Here is an example :

这是一个例子:

var ctx = canvas.getContext('2d');

var app = {};
// the total area of our drawings, can be very large now
app.WIDTH = 5000;
app.HEIGHT = 5000;

app.draw = function() {
  // reset everything (clears the canvas + transform + fillStyle + any other property of the context)
  canvas.width = canvas.width;

  // move our context by the inverse of our scrollbars' left and top property
  ctx.setTransform(1, 0, 0, 1, -app.scrollbars.left, -app.scrollbars.top);

  ctx.textAlign = 'center';
  // draw only the visible area
  var visibleLeft = app.scrollbars.left;
  var visibleWidth = visibleLeft + canvas.width;
  var visibleTop = app.scrollbars.top
  var visibleHeight = visibleTop + canvas.height;

  // you probably will have to make other calculations than these ones to get your drawings
  // to draw only where required
  for (var w = visibleLeft; w < visibleWidth + 50; w += 100) {
    for (var h = visibleTop; h < visibleHeight + 50; h += 100) {
      var x = Math.round((w) / 100) * 100;
      var y = Math.round((h) / 100) * 100;
      ctx.fillText(x + ',' + y, x, y);
    }
  }

  // draw our scrollbars on top if needed
  app.scrollbars.draw();
}

app.scrollbars = function() {
  var scrollbars = {};
  // initial position
  scrollbars.left = 0;
  scrollbars.top = 0;
  // a single constructor for both horizontal and vertical 
  var ScrollBar = function(vertical) {
    var that = {
      vertical: vertical
    };

    that.left = vertical ? canvas.width - 10 : 0;
    that.top = vertical ? 0 : canvas.height - 10;
    that.height = vertical ? canvas.height - 10 : 5;
    that.width = vertical ? 5 : canvas.width - 10;
    that.fill = '#dedede';

    that.cursor = {
      radius: 5,
      fill: '#bababa'
    };
    that.cursor.top = vertical ? that.cursor.radius : that.top + that.cursor.radius / 2;
    that.cursor.left = vertical ? that.left + that.cursor.radius / 2 : that.cursor.radius;

    that.draw = function() {
      if (!that.visible) {
        return;
      }
      // remember to reset the matrix
      ctx.setTransform(1, 0, 0, 1, 0, 0);
      // you can give it any shape you like, all canvas drawings operations are possible
      ctx.fillStyle = that.fill;
      ctx.fillRect(that.left, that.top, that.width, that.height);
      ctx.beginPath();
      ctx.arc(that.cursor.left, that.cursor.top, that.cursor.radius, 0, Math.PI * 2);
      ctx.fillStyle = that.cursor.fill;
      ctx.fill();
    };
    // check if we're hovered
    that.isHover = function(x, y) {
      if (x >= that.left - that.cursor.radius && x <= that.left + that.width + that.cursor.radius &&
        y >= that.top - that.cursor.radius && y <= that.top + that.height + that.cursor.radius) {
        // we are so record the position of the mouse and set ourself as the one hovered
        scrollbars.mousePos = vertical ? y : x;
        scrollbars.hovered = that;
        that.visible = true;
        return true;
      }
      // we were visible last call and no wheel event is happening
      else if (that.visible && !scrollbars.willHide) {
        that.visible = false;
        // the app should be redrawn
        return true;
      }
    }

    return that;
  };

  scrollbars.horizontal = ScrollBar(0);
  scrollbars.vertical = ScrollBar(1);

  scrollbars.hovered = null;
  scrollbars.dragged = null;
  scrollbars.mousePos = null;
  // check both of our scrollbars
  scrollbars.isHover = function(x, y) {
    return this.horizontal.isHover(x, y) || this.vertical.isHover(x, y);
  };
  // draw both of our scrollbars
  scrollbars.draw = function() {
    this.horizontal.draw();
    this.vertical.draw();
  };
  // check if one of our scrollbars is visible
  scrollbars.visible = function() {
    return this.horizontal.visible || this.vertical.visible;
  };
  // hide it...
  scrollbars.hide = function() {
    // only if we're not using the mousewheel or dragging the cursor
    if (this.willHide || this.dragged) {
      return;
    }
    this.horizontal.visible = false;
    this.vertical.visible = false;
  };

  // get the area's coord relative to our scrollbar
  var toAreaCoord = function(pos, scrollBar) {
    var sbBase = scrollBar.vertical ? scrollBar.top : scrollBar.left;
    var sbMax = scrollBar.vertical ? scrollBar.height : scrollBar.width;
    var areaMax = scrollBar.vertical ? app.HEIGHT - canvas.height : app.WIDTH - canvas.width;

    var ratio = (pos - sbBase) / (sbMax - sbBase);

    return areaMax * ratio;
  };

  // get the scrollbar's coord relative to our total area
  var toScrollCoords = function(pos, scrollBar) {
    var sbBase = scrollBar.vertical ? scrollBar.top : scrollBar.left;
    var sbMax = scrollBar.vertical ? scrollBar.height : scrollBar.width;
    var areaMax = scrollBar.vertical ? app.HEIGHT - canvas.height : app.WIDTH - canvas.width;

    var ratio = pos / areaMax;

    return ((sbMax - sbBase) * ratio) + sbBase;
  }

  scrollbars.scroll = function() {
      // check which one of the scrollbars is active
      var vertical = this.hovered.vertical;
      // until where our cursor can go
      var maxCursorPos = this.hovered[vertical ? 'height' : 'width'];
      var pos = vertical ? 'top' : 'left';
      // check that we're not out of the bounds
      this.hovered.cursor[pos] = this.mousePos < 0 ? 0 :
        this.mousePos > maxCursorPos ? maxCursorPos : this.mousePos;

      // seems ok so tell the app we scrolled
      this[pos] = toAreaCoord(this.hovered.cursor[pos], this.hovered);
      // redraw everything
      app.draw();
    }
    // because we will hide it after a small time
  scrollbars.willHide;
  // called by the wheel event
  scrollbars.scrollBy = function(deltaX, deltaY) {
    // it's not coming from our scrollbars
    this.hovered = null;
    // we're moving horizontally
    if (deltaX) {
      var newLeft = this.left + deltaX;
      // make sure we're in the bounds
      this.left = newLeft > app.WIDTH - canvas.width ? app.WIDTH - canvas.width : newLeft < 0 ? 0 : newLeft;
      // update the horizontal cursor
      this.horizontal.cursor.left = toScrollCoords(this.left, this.horizontal);
      // show our scrollbar
      this.horizontal.visible = true;
    }
    if (deltaY) {
      var newTop = this.top + deltaY;
      this.top = newTop > app.HEIGHT - canvas.height ? app.HEIGHT - canvas.height : newTop < 0 ? 0 : newTop;
      this.vertical.cursor.top = toScrollCoords(this.top, this.vertical);
      this.vertical.visible = true;
    }
    // if we were called less than the required timeout
    clearTimeout(this.willHide);
    this.willHide = setTimeout(function() {
      scrollbars.willHide = null;
      scrollbars.hide();
      app.draw();
    }, 500);
    // redraw everything
    app.draw();
  };

  return scrollbars;
}();

var mousedown = function(e) {
  // tell the browser we handle this
  e.preventDefault();
  // we're over one the scrollbars
  if (app.scrollbars.hovered) {
    // new promotion ! it becomes the dragged one
    app.scrollbars.dragged = app.scrollbars.hovered;
    app.scrollbars.scroll();
  }
};

var mousemove = function(e) {
  // check the coordinates of our canvas in the document
  var rect = canvas.getBoundingClientRect();
  var x = e.clientX - rect.left;
  var y = e.clientY - rect.top;
  // we're dragging something
  if (app.scrollbars.dragged) {
    // update the mouse position
    app.scrollbars.mousePos = app.scrollbars.dragged.vertical ? y : x;
    app.scrollbars.scroll();
  } else if (app.scrollbars.isHover(x, y)) {
    // something has changed, redraw to show or hide the scrollbar
    app.draw();
  }
  e.preventDefault();
};
var mouseup = function() {
  // we dropped it
  app.scrollbars.dragged = null;
};

var mouseout = function() {
  // we're out
  if (app.scrollbars.visible()) {
    app.scrollbars.hide();
    app.scrollbars.dragged = false;
    app.draw();
  }
};

var mouseWheel = function(e) {
  e.preventDefault();
  app.scrollbars.scrollBy(e.deltaX, e.deltaY);
};

canvas.addEventListener('mousemove', mousemove);
canvas.addEventListener('mousedown', mousedown);
canvas.addEventListener('mouseup', mouseup);
canvas.addEventListener('mouseout', mouseout);
canvas.addEventListener('wheel', mouseWheel);

range.onchange = function() {
  app.WIDTH = app.HEIGHT = this.value;
  app.scrollbars.left = 0;
  app.scrollbars.top = 0;
  app.draw();
};

// an initial drawing
app.draw();
canvas {border: 1px solid;}
span{font-size: .8em;}
<canvas id="canvas" width="200" height="150"></canvas>
<span>
  change the total area size
  <input type="range" min="250" max="5000000" steps="250" value="5000" id="range" />
</span>

Main advantages :

主要优势:

  • no limitation for the size of your drawing areas.
  • you can customize your scrollbars as you wish.
  • you can control when the scrollbars are enable or not.
  • you can get the visible area quite easily.
  • 对绘图区域的大小没有限制。
  • 您可以根据需要自定义滚动条。
  • 您可以控制何时启用滚动条。
  • 您可以很容易地获得可见区域。

Main caveats:

主要注意事项:

  • a bit more code than the CSS solution...
  • no really, that's a lot of code...
  • 比 CSS 解决方案多一点代码......
  • 不,那是很多代码......


A third wayI wrote some time ago for an other question took advantage of the ability to draw an other canvas with ctx.drawImage(). It has its own caveats and advantages, so I let you pick the one you need, but this last one also had a drag and slide feature which can be useful.

我前段时间为另一个问题写的第三种方法利用了使用ctx.drawImage(). 它有自己的注意事项和优点,所以我让你选择你需要的,但最后一个也有一个拖拽和滑动功能,很有用。