Konvajs:创建一个对其中一个孩子有一些约束的可拖动区域
Konvajs: Create a draggable area with some constraints on one of the childs
我正在使用 Konva 创建一个时间轴,整个时间轴(舞台)可以在所有方向上拖动,但我有一个包含时间轴(Konva 组)所有年份的轴,我想限制它的移动,以便它只能水平移动。
我不能使用dragBoundFunc,因为它会限制时间轴所有节点上的移动。
我尝试使用 dragmove 事件更改元素的位置:
stage.on("dragmove", function(evt) {
xaxis.y(0);
});
但是拖动舞台时x轴仍然在各个方向移动。
我也可以为轴和时间轴本身使用不同的可拖动层,但是当我拖动轴时它不会移动时间轴,如果我移动时间轴也是如此。
不完全清楚你在问什么,但我假设你想限制时间线的拖拽,以便它提供良好的用户体验。请参阅下面的工作片段。大部分代码是时间轴的设置。重要的是
包括一个覆盖整个时间线的矩形,该矩形具有零不透明度并且正在侦听鼠标事件。
为图层提供一个 dragBoundFunc,returns 一个对象作为 {x: val, y: val},其中 x 被限制以阻止用户水平拖动到视线之外,并且 y 不允许更改。如果您将 rect 和 stage 视为矩形,那么数学就不难理解了。如果您的时间轴是垂直的,请交换 x 和 y 行为。
var stageWidth = 800,
timeFrom = 1960,
timeTo = 2060,
range = timeTo - timeFrom,
timeLineWidth = 1000;
yearWidth = timeLineWidth / range,
events = [{
date: 1964,
desc: 'Born'
},
{
date: 1968,
desc: 'Infant school'
},
{
date: 1975,
desc: 'Secondary school'
},
{
date: 1981,
desc: 'Sixth form'
},
{
date: 1983,
desc: 'University'
},
{
date: 1986,
desc: 'Degree, entered IT career'
},
{
date: 1990,
desc: 'Marriage #1'
},
{
date: 1998,
desc: 'Marriage #2'
},
{
date: 1999,
desc: 'Son born'
},
{
date: 2025,
desc: 'Retired ?'
},
{
date: 2044,
desc: 'Enters Duncodin - retirement home for IT workers'
},
{
date: 2054,
desc: 'Star dust'
}
]
function setup() {
// Set up a stage and a shape
stage = new Konva.Stage({
container: 'konva-stage',
width: stageWidth,
height: 500
});
layer = new Konva.Layer({
draggable: true,
// the dragBoundFunc returns an object as {x: val, y: val} in which the x is constricted to stop
// the user dragging out of sight, and the y is not allowed to change.
dragBoundFunc: function(pos) {
return {
x: function() {
retX = pos.x;
if (retX > 20) {
retX = 20;
} else if (retX < (stageWidth - (timeLineWidth + 50))) {
retX = stageWidth - (timeLineWidth + 50);
}
return retX;
}(),
y: this.absolutePosition().y
};
}
});
stage.add(layer);
// add timeline
var timeLine = new Konva.Rect({
x: 0,
y: 245,
height: 10,
width: timeLineWidth,
fill: 'magenta',
listening: false
});
layer.add(timeLine)
for (var i = 0, max = events.length; i < max; i = i + 1) {
var event = events[i];
var link = new Konva.Rect({
x: yearWidth * (event.date - timeFrom),
y: 200,
width: 5,
height: 55,
fill: 'magenta',
listening: false
});
layer.add(link)
var timeLabel = new Konva.Text({
x: yearWidth * (event.date - timeFrom) + 10,
y: 265,
text: event.date,
fontSize: 16,
fontFamily: 'Calibri',
fill: 'magenta',
rotation: 90,
listening: false
});
layer.add(timeLabel);
var eventLabel = new Konva.Text({
x: yearWidth * (event.date - timeFrom) - 5,
y: 190,
text: event.desc,
fontSize: 16,
fontFamily: 'Calibri',
fill: 'magenta',
rotation: -90,
listening: false
});
layer.add(eventLabel);
var dragRect = new Konva.Rect({
x: 0,
y: 0,
width: timeLineWidth,
height: 500,
opacity: 0,
fill: 'cyan',
listening: true
});
layer.add(dragRect);
dragRect.moveToTop()
}
stage.draw()
}
var stage, layer;
setup()
.konva-stage {
width: 100%;
height: 100%;
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/konva/4.0.13/konva.js"></script>
<p>Drag the timeline...</p>
<div id="konva-stage"></div>
这里有一个稍微更强大的版本,它允许垂直拖动事件层,同时保持时间轴可见以供参考。这使用了两层 - 一层用作包含时间线和网格的背景,而第二层显示事件。
这里的关键技术是在可拖动事件层上使用 dragMove 事件侦听器水平同步移动背景层,但不是垂直移动。同时,事件层也受到 dragBound 函数的约束,以阻止愚蠢的 UX。
一个改进是向事件层添加剪辑,这样当向下拖动时它不会遮挡时间轴。
var stageWidth = 800,
stageHeight = 300,
timeFrom = 1960,
timeTo = 2060,
timeRange = timeTo - timeFrom,
timeLineWidth = 1000,
timeSteps = 20, // over 100 yrs = 5 year intervals
timeInt = timeRange / timeSteps,
timeLineStep = timeLineWidth / timeSteps,
yearWidth = timeLineWidth / timeRange,
plotHeight = 500,
events = [{
date: 1964,
desc: 'Born',
dist: 10
},
{
date: 1966,
desc: 'England win world cup - still celebrating !',
dist: 20
},
{
date: 1968,
desc: 'Infant school',
dist: 30
},
{
date: 1975,
desc: 'Secondary school',
dist: 50
},
{
date: 1981,
desc: 'Sixth form',
dist: 7
},
{
date: 1983,
desc: 'University',
dist: 30
},
{
date: 1986,
desc: 'Degree, entered IT career',
dist: 50
},
{
date: 1990,
desc: 'Marriage #1',
dist: 0
},
{
date: 1996,
desc: 'Divorce #1',
dist: 0
},
{
date: 1998,
desc: 'Marriage #2 & Son born',
dist: 90
},
{
date: 2000,
desc: 'World did not end',
dist: 20
},
{
date: 2025,
desc: 'Retired ?',
dist: 0
},
{
date: 2044,
desc: 'Enters Duncodin - retirement home for IT workers',
dist: 0
},
{
date: 2054,
desc: 'Star dust',
dist: 0
}
]
function setup() {
// Set up a stage and a shape
stage = new Konva.Stage({
container: 'konva-stage',
width: stageWidth,
height: stageHeight
});
// bgLayer is the background with the grid, timeline and date text.
var bgLayer = new Konva.Layer({
draggable: false
})
stage.add(bgLayer);
for (var i = 0, max = timeSteps; i < max; i = i + 1) {
bgLayer.add(new Konva.Line({
points: [(i * timeLineStep) + 0.5, 0, (i * timeLineStep) + .5, plotHeight],
stroke: 'cyan',
strokeWidth: 1
}))
bgLayer.add(new Konva.Text({
x: (i * timeLineStep) + 4,
y: 260,
text: timeFrom + (timeInt * i),
fontSize: 12,
fontFamily: 'Calibri',
fill: 'magenta',
rotation: 90,
listening: false
}));
}
for (var i = 0, max = plotHeight; i < max; i = i + timeLineStep) {
bgLayer.add(new Konva.Line({
points: [0, i + 0.5, timeLineWidth, i + .5],
stroke: 'cyan',
strokeWidth: 1
}))
}
// add timeline
var timeLine = new Konva.Rect({
x: 0,
y: 245,
height: 1,
width: timeLineWidth,
fill: 'magenta',
listening: false
});
bgLayer.add(timeLine)
// eventLayer contains only the event link line and text.
var eventLayer = new Konva.Layer({
draggable: true,
// the dragBoundFunc returns an object as {x: val, y: val} in which the x is constricted to stop
// the user dragging out of sight, and the y is not allowed to change.
// ! position of bgLayer is moved in x axis in sync with eventLayer via dragMove event
dragBoundFunc: function(pos) {
return {
x: function() {
var retX = pos.x;
if (retX > 20) { // if the left exceeds 20px from left edge of stage
retX = 20;
} else if (retX < (stageWidth - (timeLineWidth + 50))) { // if the right exceeds 50 px from right edge of stage
retX = stageWidth - (timeLineWidth + 50);
}
return retX;
}(),
y: function() {
var retY = pos.y;
if (retY < 0) {
retY = 0;
} else if (retY > 200) {
retY = 200;
}
return retY;
}()
};
}
});
stage.add(eventLayer);
// ! position of bgLayer is moved in x axis in sync with eventLayer via dragMove event of eventLayer.
eventLayer.on('dragmove', function() {
var pos = eventLayer.position();
var bgPos = bgLayer.position();
bgLayer.position({
x: pos.x,
y: bgPos.y
}); // <--- move the bgLayer in sync with the event eventLayer.
stage.draw()
});
for (var i = 0, max = events.length; i < max; i = i + 1) {
var event = events[i];
var link = new Konva.Rect({
x: yearWidth * (event.date - timeFrom),
y: 200 - event.dist,
width: 1,
height: 55 + event.dist,
fill: 'magenta',
listening: false
});
eventLayer.add(link)
var eventLabel = new Konva.Text({
x: yearWidth * (event.date - timeFrom) - 5,
y: 190 - event.dist,
text: event.date + ' - ' + event.desc,
fontSize: 16,
fontFamily: 'Calibri',
fill: 'magenta',
rotation: -90,
listening: false
});
eventLayer.add(eventLabel);
var dragRect = new Konva.Rect({
x: 0,
y: 0,
width: timeLineWidth,
height: 500,
opacity: 0,
fill: 'cyan',
listening: true
});
eventLayer.add(dragRect);
dragRect.moveToTop()
}
stage.draw()
}
var stage, eventLayer;
setup()
.konva-stage {
width: 100%;
height: 100%;
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/konva/4.0.13/konva.js"></script>
<p>Drag the timeline left & right AND up & down...</p>
<div id="konva-stage"></div>
作为最简单的解决方案,您只需确保您的timelime组的绝对位置相同即可:
stage.on("dragmove", function(evt) {
// read absolute position
const oldAbs = xaxis.absolutePosition();
// set new absolute position, but make sure x = 0
xaxis.absolutePosition({
x: oldAbs.x,
y: 0
});
});
只是为了好玩,我的答案的精简版本显示了 ondrag() 函数,没有所有时间线装饰。
var stage;
function setup() {
// Set up a stage and a shape
stage = new Konva.Stage({
container: 'konva-stage',
width: 600,
height: 300
});
// layer1.
var layer1 = new Konva.Layer({
draggable: false
})
stage.add(layer1);
var ln1 = new Konva.Line({
points: [10, 0, 10, 20, 10, 10, 0, 10, 20, 10],
stroke: 'cyan',
strokeWidth: 4
});
layer1.add(ln1);
var layer2 = new Konva.Layer({
draggable: true,
});
stage.add(layer2);
var ln2 = new Konva.Line({
points: [10, 0, 10, 20, 10, 10, 0, 10, 20, 10],
stroke: 'magenta',
strokeWidth: 4
});
layer2.add(ln2);
// position the crosses on the canvas
ln1.position({
x: 100,
y: 80
});
ln2.position({
x: 100,
y: 40
});
// ! position of layer1 is moved in x axis in sync with layer2 via dragMove event of layer2.
layer2.on('dragmove', function() {
var pos = layer2.position();
var bgPos = layer1.position();
layer1.position({
x: pos.x,
y: bgPos.y
}); // <--- move layer1 in sync with layer2.
stage.draw()
});
stage.draw()
}
setup()
.konva-stage {
width: 100%;
height: 100%;
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/konva/4.0.13/konva.js"></script>
<p>Drag the upper cross - only one moves vertically whilst the other is contrained in the y-axis. Both move in sync on the x-axis</p>
<div id="konva-stage"></div>
我正在使用 Konva 创建一个时间轴,整个时间轴(舞台)可以在所有方向上拖动,但我有一个包含时间轴(Konva 组)所有年份的轴,我想限制它的移动,以便它只能水平移动。
我不能使用dragBoundFunc,因为它会限制时间轴所有节点上的移动。
我尝试使用 dragmove 事件更改元素的位置:
stage.on("dragmove", function(evt) {
xaxis.y(0);
});
但是拖动舞台时x轴仍然在各个方向移动。
我也可以为轴和时间轴本身使用不同的可拖动层,但是当我拖动轴时它不会移动时间轴,如果我移动时间轴也是如此。
不完全清楚你在问什么,但我假设你想限制时间线的拖拽,以便它提供良好的用户体验。请参阅下面的工作片段。大部分代码是时间轴的设置。重要的是
包括一个覆盖整个时间线的矩形,该矩形具有零不透明度并且正在侦听鼠标事件。
为图层提供一个 dragBoundFunc,returns 一个对象作为 {x: val, y: val},其中 x 被限制以阻止用户水平拖动到视线之外,并且 y 不允许更改。如果您将 rect 和 stage 视为矩形,那么数学就不难理解了。如果您的时间轴是垂直的,请交换 x 和 y 行为。
var stageWidth = 800,
timeFrom = 1960,
timeTo = 2060,
range = timeTo - timeFrom,
timeLineWidth = 1000;
yearWidth = timeLineWidth / range,
events = [{
date: 1964,
desc: 'Born'
},
{
date: 1968,
desc: 'Infant school'
},
{
date: 1975,
desc: 'Secondary school'
},
{
date: 1981,
desc: 'Sixth form'
},
{
date: 1983,
desc: 'University'
},
{
date: 1986,
desc: 'Degree, entered IT career'
},
{
date: 1990,
desc: 'Marriage #1'
},
{
date: 1998,
desc: 'Marriage #2'
},
{
date: 1999,
desc: 'Son born'
},
{
date: 2025,
desc: 'Retired ?'
},
{
date: 2044,
desc: 'Enters Duncodin - retirement home for IT workers'
},
{
date: 2054,
desc: 'Star dust'
}
]
function setup() {
// Set up a stage and a shape
stage = new Konva.Stage({
container: 'konva-stage',
width: stageWidth,
height: 500
});
layer = new Konva.Layer({
draggable: true,
// the dragBoundFunc returns an object as {x: val, y: val} in which the x is constricted to stop
// the user dragging out of sight, and the y is not allowed to change.
dragBoundFunc: function(pos) {
return {
x: function() {
retX = pos.x;
if (retX > 20) {
retX = 20;
} else if (retX < (stageWidth - (timeLineWidth + 50))) {
retX = stageWidth - (timeLineWidth + 50);
}
return retX;
}(),
y: this.absolutePosition().y
};
}
});
stage.add(layer);
// add timeline
var timeLine = new Konva.Rect({
x: 0,
y: 245,
height: 10,
width: timeLineWidth,
fill: 'magenta',
listening: false
});
layer.add(timeLine)
for (var i = 0, max = events.length; i < max; i = i + 1) {
var event = events[i];
var link = new Konva.Rect({
x: yearWidth * (event.date - timeFrom),
y: 200,
width: 5,
height: 55,
fill: 'magenta',
listening: false
});
layer.add(link)
var timeLabel = new Konva.Text({
x: yearWidth * (event.date - timeFrom) + 10,
y: 265,
text: event.date,
fontSize: 16,
fontFamily: 'Calibri',
fill: 'magenta',
rotation: 90,
listening: false
});
layer.add(timeLabel);
var eventLabel = new Konva.Text({
x: yearWidth * (event.date - timeFrom) - 5,
y: 190,
text: event.desc,
fontSize: 16,
fontFamily: 'Calibri',
fill: 'magenta',
rotation: -90,
listening: false
});
layer.add(eventLabel);
var dragRect = new Konva.Rect({
x: 0,
y: 0,
width: timeLineWidth,
height: 500,
opacity: 0,
fill: 'cyan',
listening: true
});
layer.add(dragRect);
dragRect.moveToTop()
}
stage.draw()
}
var stage, layer;
setup()
.konva-stage {
width: 100%;
height: 100%;
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/konva/4.0.13/konva.js"></script>
<p>Drag the timeline...</p>
<div id="konva-stage"></div>
这里有一个稍微更强大的版本,它允许垂直拖动事件层,同时保持时间轴可见以供参考。这使用了两层 - 一层用作包含时间线和网格的背景,而第二层显示事件。
这里的关键技术是在可拖动事件层上使用 dragMove 事件侦听器水平同步移动背景层,但不是垂直移动。同时,事件层也受到 dragBound 函数的约束,以阻止愚蠢的 UX。
一个改进是向事件层添加剪辑,这样当向下拖动时它不会遮挡时间轴。
var stageWidth = 800,
stageHeight = 300,
timeFrom = 1960,
timeTo = 2060,
timeRange = timeTo - timeFrom,
timeLineWidth = 1000,
timeSteps = 20, // over 100 yrs = 5 year intervals
timeInt = timeRange / timeSteps,
timeLineStep = timeLineWidth / timeSteps,
yearWidth = timeLineWidth / timeRange,
plotHeight = 500,
events = [{
date: 1964,
desc: 'Born',
dist: 10
},
{
date: 1966,
desc: 'England win world cup - still celebrating !',
dist: 20
},
{
date: 1968,
desc: 'Infant school',
dist: 30
},
{
date: 1975,
desc: 'Secondary school',
dist: 50
},
{
date: 1981,
desc: 'Sixth form',
dist: 7
},
{
date: 1983,
desc: 'University',
dist: 30
},
{
date: 1986,
desc: 'Degree, entered IT career',
dist: 50
},
{
date: 1990,
desc: 'Marriage #1',
dist: 0
},
{
date: 1996,
desc: 'Divorce #1',
dist: 0
},
{
date: 1998,
desc: 'Marriage #2 & Son born',
dist: 90
},
{
date: 2000,
desc: 'World did not end',
dist: 20
},
{
date: 2025,
desc: 'Retired ?',
dist: 0
},
{
date: 2044,
desc: 'Enters Duncodin - retirement home for IT workers',
dist: 0
},
{
date: 2054,
desc: 'Star dust',
dist: 0
}
]
function setup() {
// Set up a stage and a shape
stage = new Konva.Stage({
container: 'konva-stage',
width: stageWidth,
height: stageHeight
});
// bgLayer is the background with the grid, timeline and date text.
var bgLayer = new Konva.Layer({
draggable: false
})
stage.add(bgLayer);
for (var i = 0, max = timeSteps; i < max; i = i + 1) {
bgLayer.add(new Konva.Line({
points: [(i * timeLineStep) + 0.5, 0, (i * timeLineStep) + .5, plotHeight],
stroke: 'cyan',
strokeWidth: 1
}))
bgLayer.add(new Konva.Text({
x: (i * timeLineStep) + 4,
y: 260,
text: timeFrom + (timeInt * i),
fontSize: 12,
fontFamily: 'Calibri',
fill: 'magenta',
rotation: 90,
listening: false
}));
}
for (var i = 0, max = plotHeight; i < max; i = i + timeLineStep) {
bgLayer.add(new Konva.Line({
points: [0, i + 0.5, timeLineWidth, i + .5],
stroke: 'cyan',
strokeWidth: 1
}))
}
// add timeline
var timeLine = new Konva.Rect({
x: 0,
y: 245,
height: 1,
width: timeLineWidth,
fill: 'magenta',
listening: false
});
bgLayer.add(timeLine)
// eventLayer contains only the event link line and text.
var eventLayer = new Konva.Layer({
draggable: true,
// the dragBoundFunc returns an object as {x: val, y: val} in which the x is constricted to stop
// the user dragging out of sight, and the y is not allowed to change.
// ! position of bgLayer is moved in x axis in sync with eventLayer via dragMove event
dragBoundFunc: function(pos) {
return {
x: function() {
var retX = pos.x;
if (retX > 20) { // if the left exceeds 20px from left edge of stage
retX = 20;
} else if (retX < (stageWidth - (timeLineWidth + 50))) { // if the right exceeds 50 px from right edge of stage
retX = stageWidth - (timeLineWidth + 50);
}
return retX;
}(),
y: function() {
var retY = pos.y;
if (retY < 0) {
retY = 0;
} else if (retY > 200) {
retY = 200;
}
return retY;
}()
};
}
});
stage.add(eventLayer);
// ! position of bgLayer is moved in x axis in sync with eventLayer via dragMove event of eventLayer.
eventLayer.on('dragmove', function() {
var pos = eventLayer.position();
var bgPos = bgLayer.position();
bgLayer.position({
x: pos.x,
y: bgPos.y
}); // <--- move the bgLayer in sync with the event eventLayer.
stage.draw()
});
for (var i = 0, max = events.length; i < max; i = i + 1) {
var event = events[i];
var link = new Konva.Rect({
x: yearWidth * (event.date - timeFrom),
y: 200 - event.dist,
width: 1,
height: 55 + event.dist,
fill: 'magenta',
listening: false
});
eventLayer.add(link)
var eventLabel = new Konva.Text({
x: yearWidth * (event.date - timeFrom) - 5,
y: 190 - event.dist,
text: event.date + ' - ' + event.desc,
fontSize: 16,
fontFamily: 'Calibri',
fill: 'magenta',
rotation: -90,
listening: false
});
eventLayer.add(eventLabel);
var dragRect = new Konva.Rect({
x: 0,
y: 0,
width: timeLineWidth,
height: 500,
opacity: 0,
fill: 'cyan',
listening: true
});
eventLayer.add(dragRect);
dragRect.moveToTop()
}
stage.draw()
}
var stage, eventLayer;
setup()
.konva-stage {
width: 100%;
height: 100%;
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/konva/4.0.13/konva.js"></script>
<p>Drag the timeline left & right AND up & down...</p>
<div id="konva-stage"></div>
作为最简单的解决方案,您只需确保您的timelime组的绝对位置相同即可:
stage.on("dragmove", function(evt) {
// read absolute position
const oldAbs = xaxis.absolutePosition();
// set new absolute position, but make sure x = 0
xaxis.absolutePosition({
x: oldAbs.x,
y: 0
});
});
只是为了好玩,我的答案的精简版本显示了 ondrag() 函数,没有所有时间线装饰。
var stage;
function setup() {
// Set up a stage and a shape
stage = new Konva.Stage({
container: 'konva-stage',
width: 600,
height: 300
});
// layer1.
var layer1 = new Konva.Layer({
draggable: false
})
stage.add(layer1);
var ln1 = new Konva.Line({
points: [10, 0, 10, 20, 10, 10, 0, 10, 20, 10],
stroke: 'cyan',
strokeWidth: 4
});
layer1.add(ln1);
var layer2 = new Konva.Layer({
draggable: true,
});
stage.add(layer2);
var ln2 = new Konva.Line({
points: [10, 0, 10, 20, 10, 10, 0, 10, 20, 10],
stroke: 'magenta',
strokeWidth: 4
});
layer2.add(ln2);
// position the crosses on the canvas
ln1.position({
x: 100,
y: 80
});
ln2.position({
x: 100,
y: 40
});
// ! position of layer1 is moved in x axis in sync with layer2 via dragMove event of layer2.
layer2.on('dragmove', function() {
var pos = layer2.position();
var bgPos = layer1.position();
layer1.position({
x: pos.x,
y: bgPos.y
}); // <--- move layer1 in sync with layer2.
stage.draw()
});
stage.draw()
}
setup()
.konva-stage {
width: 100%;
height: 100%;
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/konva/4.0.13/konva.js"></script>
<p>Drag the upper cross - only one moves vertically whilst the other is contrained in the y-axis. Both move in sync on the x-axis</p>
<div id="konva-stage"></div>