问题
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