【问题标题】:Html5 canvas scrolling vertically and horizontallyHtml5画布垂直和水平滚动
【发布时间】:2016-07-13 04:35:31
【问题描述】:
<!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. 以下画布动画创建节点和边。然而由于 受空间限制,部分节点因画布不可见 高度和宽度。即使将溢出的 css 添加到画布也无济于事 我无法滚动。

【问题讨论】:

  • canvas 上下文没有内置的滚动方法,要么使用translate、transform 或setTransform 方法自己实现它,要么将你的画布设置为你需要的大小,并且将其包装为具有所需屏幕尺寸的其他元素,并向该元素添加溢出 css 属性。
  • 你能把上面写的代码或者一些示例修复一下吗

标签: html html5-canvas


【解决方案1】:

&lt;canvas&gt; 上下文没有内置的滚动方法。

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

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


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


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

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

在以下示例中,画布宽 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>

主要优势:

  • 很容易实现。
  • 用户习惯了这些滚动条。

主要注意事项:

  • 你被canvas maximum sizes限制了。
  • 如果您的画布是动画的,您还将为画布的每帧不可见部分进行绘制。
  • 您可以对滚动条外观进行少量控制,但您仍然需要自己为桌面浏览器实现拖动滚动功能。

第二种解决方案是自己实现此功能,使用画布转换方法:特别是translate、transform 和setTransform。

这是一个例子:

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>

主要优势:

  • 绘图区域的大小没有限制。
  • 您可以根据需要自定义滚动条。
  • 您可以控制滚动条何时启用。
  • 您可以很容易地获得可见区域。

主要注意事项:

  • 比 CSS 解决方案多一点代码...
  • 不,真的,这是很多代码...

A third way 我前段时间写了一个问题,利用ctx.drawImage() 绘制另一个画布的能力。它有自己的注意事项和优点,所以我让你选择你需要的那个,但最后一个也有一个非常有用的拖放功能。

【讨论】:

【解决方案2】:

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

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

context.scale(horizontalRescale,verticalRescale) 命令将按照您指定的水平缩放百分比和垂直缩放百分比缩小每个后续图形。

重要提示:您必须使水平重新缩放,垂直重新缩放相同的值,否则您的内容将被扭曲。

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

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

var downscaleFactor= 0.80;

context.scale( downscaleFactor, downscaleFactor );

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

【讨论】:

  • 它有效。谢谢。除了缩放选项。是否可以水平和垂直拖动画布,以便用户只需滚动即可看到所有可能的节点或者如果我单击上节点,则连接到它的所有节点都将可见,并且画布将自动滚动到该位置
  • 是的,自动滚动可以通过重绘整个画布但水平和垂直拉动直到你想要的节点可见。就像我的回答中的context.scale 一样,您可以使用context.translate( horizontalOffset, verticalOffset) 将您想要的节点拉到视图中。无法保证您所需的所有节点都适合可用的画布大小,因此您可能仍需要执行context.scale。祝你的项目好运! :-)
猜你喜欢
  • 1970-01-01
  • 2014-01-09
  • 1970-01-01
  • 2012-12-31
  • 2020-08-17
  • 2015-05-31
  • 2013-11-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多