Create a div like element that has overflow set to auto using HTML Canvas

假如想象 提交于 2019-12-13 02:15:43

问题


The title might be misleading but that is the best I could come up with for a summary of my question.

Anyways, I need to figure out how to make a list, or a container, in this case a plain rectangle that contains a list of items, which can be dragged up and down in order to reveal other items in the container. In a way it would resemble a constrained div with a slider bar, but without the slider.

Now, I have an idea on using KonvaJS, former KineticJS to put all the items in the container in a group, and make the group draggable in certain directions, etc.

However the catch is that the sliding of the elements top or down should not only be on drag, but on flick also. So if you kind of flick your finger/mouse upwards the list would keep sliding by, until the end, where the speed would vary based on the flick intensity. If determining the flick intensity or speed is too complicated, then just any type of flick would need to slide the whole list to the bottom, or top.

So this should kind of resemble the standard vertical slide widgets you have on your android or ios. Now do you have any ideas on how I can proceed with this, or how would you go about this. Any ideas are welcome.


回答1:


Working demo: http://jsbin.com/gefuvu/edit?js,output

Usual drag and drop is already supported by draggable property. For limit drag&drop to vertical scrolling I am using this simple dragBound:

const group = new Konva.Group({
  draggable: true,
  dragBoundFunc: (pos) => {
    const minY = -group.getClientRect().height + stage.height();
    const maxY = 0;
    const y = Math.max(Math.min(pos.y, maxY), minY);

    return {y, x: 0}
  }
});

"Flick" implementation:

// setup flick
let lastY = null;
let dY = 0;
group.on('dragstart', () => {
  lastY = group.y();
  dy = 0;
});
group.on('dragmove', () => {
    dy = lastY - group.y();
    lastY = group.y();
});
group.on('dragend', () => {
    // if last move is far way it means user move pointer very fast
    // for this case we need to automatically "scroll" group
    if (dy > 5) {
        group.to({
          y: -group.getClientRect().height + stage.height()
        });
    }
    if (dy < -5) {
        group.to({
          y: 0
        });
    }
});



回答2:


I guess that when you talk about "flick" you actually mean "scroll".
Edit : Missed the point of the question, also missed the [konvajs] tag. But here is a way to do it without any library, hoping it may help someone coming this way.

The simplest idea is to make two objects, a container and a content, each one with a canvas.

On mouse's wheel event, update the content position, then redraw its canvas to the container's one or if you need to handle drag, listen to the mousemove event, set a dragging flag to true, that you remove on mouseup. On mousemove update the position after you calculated the moving speed by checking the last event's timestamp and the new one's. Then on mouseup, start an animation that will decrease the speed of your movement :

// our container object
var container = {
  width: window.innerWidth - 2,
  height: window.innerHeight - 2,
  top: 0,
  left: 0,
  canvas: document.getElementById('container'),
  isOver: function(x, y) {
    return (x >= this.left && x <= this.left + this.width &&
      y >= this.top && y <= this.top + this.height);
  },
};
// our content object
var content = {
  width: container.width * 2,
  height: container.height * 2,
  top: 0,
  left: 0,
  background: 'rgba(0,255,0,.5)',
  canvas: document.createElement('canvas'),
  // set an init function to draw the texts
  init: function() {
    var ctx = this.ctx;
    ctx.font = '20px sans-serif';
    ctx.textBaseline = 'top';
    ctx.fillText('Hello World', 0, 0);
    ctx.textBaseline = 'middle';
    ctx.textAlign = 'center';
    ctx.fillText('Middle world', this.width / 2, this.height / 2);
    ctx.textBaseline = 'bottom';
    ctx.textAlign = 'left';
    var textLength = ctx.measureText('Bye World').width;
    ctx.fillText('Bye World', this.canvas.width - textLength, this.canvas.height);
    ctx.fillStyle = this.background;
    ctx.fillRect(0, 0, this.width, this.height);
  },
};
// init the objects
var init = function(obj) {
    var c = obj.canvas;
    obj.ctx = c.getContext('2d');
    c.width = obj.width;
    c.height = obj.height;
    if (obj.init) {
      obj.init();
    }
  }
  // our drawing function
var draw = function() {
  container.ctx.clearRect(0, 0, container.width, container.height);
  container.ctx.drawImage(content.canvas, content.left, content.top);
};
// update the content position
container.update = function(x, y) {
  // if the content is smaller, we don't need to scroll 
  if (content.width > container.width) {
    var maxX = Math.max(container.width, content.width);
    var minX = Math.min(container.width, content.width);

    content.left -= x;
    // if we are at one end
    if (content.left < minX - maxX) {
      content.left = minX - maxX;
    } // or another
    else if (content.left > 0) {
      content.left = 0;
    }
  }
  if (content.height > container.height) {
    var maxY = Math.max(container.height, content.height);
    var minY = Math.min(container.height, content.height);

    content.top -= y;
    if (content.top < minY - maxY) {
      content.top = minY - maxY;
    } else if (content.top > 0) {
      content.top = 0;
    }
  }
};

var drag = {
  friction: .1,
  sensibility: 18,
  minSpeed: .01,
};

var mouseMove_Handler = function(e) {
  // we're not dragging anything, stop here
  if (!drag.dragged) {
    return;
  }

  var rect = this.getBoundingClientRect();
  var posX = e.clientX - rect.left;
  var posY = e.clientY - rect.top;
  // how long did it take since last event
  var deltaTime = (e.timeStamp - drag.lastDragTime) / drag.sensibility;
  // our moving speed
  var deltaX = (drag.lastDragX - posX) / deltaTime;
  var deltaY = (drag.lastDragY - posY) / deltaTime;
  // update the drag object
  drag.lastDragX = posX;
  drag.lastDragY = posY;
  drag.lastDeltaX = deltaX;
  drag.lastDeltaY = deltaY;
  drag.lastDragTime = e.timeStamp;
  // update the container obj
  drag.dragged.update(deltaX, deltaY);
  // redraw
  draw();
};

var mouseDown_Handler = function(e) {
  // if we are sliding, stop it
  if (drag.sliding) {
    cancelAnimationFrame(drag.sliding);
    drag.sliding = null;
  }

  var rect = this.getBoundingClientRect();
  var posX = e.clientX - rect.left;
  var posY = e.clientY - rect.top;
  // first check that the event occurred on top of our container object
  // we could loop through multiple ones
  if (container.isOver(posX, posY)) {
    // init our drag object
    drag.dragged = container;
    drag.lastDragX = posX;
    drag.lastDragY = posY;
    drag.lastDragTime = e.timeStamp;

  }
};

var mouseUp_Handler = function(e) {
  // store a ref of which object we were moving
  var container = drag.dragged;
  // we're not dragging anymore
  drag.dragged = false;
  var slide = function() {
    // decrease the speed
    drag.lastDeltaX /= 1 + drag.friction;
    drag.lastDeltaY /= 1 + drag.friction;
    // check that we are still out of our minimum speed
    if (drag.lastDeltaX > drag.minSpeed || drag.lastDeltaY > drag.minSpeed ||
      drag.lastDeltaX < -drag.minSpeed || drag.lastDeltaY < -drag.minSpeed) {
      // store a reference of the animation 
      drag.sliding = requestAnimationFrame(slide);
    } else {
      drag.sliding = null;
      drag.lastDeltaX = drag.lastDeltaY = 0;
    }
    container.update(drag.lastDeltaX, drag.lastDeltaY);
    draw();
  };
  slide();
};

// add the wheel listener, for a polyfill check the MDN page : 
// https://developer.mozilla.org/en-US/docs/Web/Events/wheel#Listening_to_this_event_across_browser
var mouseWheel_Handler = function(e) {
  // get the position of our canvas element
  var rect = this.getBoundingClientRect();
  var posX = e.clientX - rect.left;
  var posY = e.clientY - rect.top;
  // first check that the event occurred on top of our container object
  if (container.isOver(posX, posY)) {
    // tell the browser we handle it
    e.preventDefault();
    e.stopPropagation();
    // send the event's deltas
    container.update(e.deltaX, e.deltaY);
    // redraw
    draw();
  }
};

container.canvas.addEventListener('mousedown', mouseDown_Handler);
container.canvas.addEventListener('mousemove', mouseMove_Handler);
container.canvas.addEventListener('mouseup', mouseUp_Handler);
container.canvas.addEventListener('mouseleave', mouseUp_Handler);
container.canvas.addEventListener('wheel', mouseWheel_Handler);

// init the objects
init(container);
init(content);
// make a first draw
draw();


// Snippet only preventions \\

// avoid the outer window to scroll
window.onscroll = function(e) {
  e.preventDefault();
  e.stopPropagation()
};

// if you go in full page view
window.onresize = function() {
  container.width = window.innerWidth;
  container.height = window.innerHeight;
  content.width = container.width * 2;
  content.height = container.height * 2;

  init(container);
  init(content);

  draw();
};
body,html,canvas {
  margin: 0;
  display: block
}
canvas {
  border: 1px solid;
}
<canvas id="container"></canvas>


来源:https://stackoverflow.com/questions/34413011/create-a-div-like-element-that-has-overflow-set-to-auto-using-html-canvas

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!