使用 HTML Canvas 创建一个类似 div 的元素,将溢出设置为自动

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

标题可能会产生误导,但这是我能想到的最好的问题摘要。

无论如何,我需要弄清楚如何制作列表或容器,在本例中是包含项目列表的普通矩形,可以上下拖动以显示列表中的其他项目容器。在某种程度上,它类似于带有滑块但没有滑块的约束 div。

现在,我想到了使用 KonvaJS,以前的 KineticJS 将容器中的所有项目放在一个组中,并使组可以在某些方向上拖动等。

不过要注意的是,元素向上或向下的滑动不仅要拖动,还要轻弹。因此,如果您向上轻弹 finger/mouse,列表将一直滑动,直到结束,速度将根据轻弹强度而变化。如果确定轻弹强度或速度太复杂,那么任何类型的轻弹都需要将整个列表滑动到底部或顶部。

所以这应该类似于您在 android 或 ios 上的标准垂直滑动小部件。现在你对我如何处理这个有什么想法,或者你会如何处理这个。欢迎任何想法。

我猜你说的"flick"其实是指"scroll".
编辑: 错过了问题的重点,也错过了 [konvajs] 标签。但是这里有一种方法可以在没有任何图书馆的情况下做到这一点,希望它可以帮助那些以这种方式来的人。

最简单的想法是制作两个对象,一个容器和一个内容,每个对象都有一个canvas。

在鼠标的 wheel 事件上,更新内容位置,然后将其 canvas 重绘到容器的位置,或者如果您需要处理拖动,请监听 mousemove 事件,设置dragging 标志为真,您在 mouseup 上将其删除。在 mousemove 通过检查上一个事件的时间戳和新事件的时间戳计算移动速度后更新位置。然后在 mouseup,启动一个会降低你移动速度的动画:

// 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>

工作演示:http://jsbin.com/gefuvu/edit?js,output

draggable属性 已经支持通常的拖放操作。为了将拖放限制为垂直滚动,我使用了这个简单的 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" 实施:

// 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
        });
    }
});