How to drag and drop items between 2 columns with "Konva.JS"? - javascript

I'm looking for a way to drag and drop items between 2 columns with Konva.JS.
Since I found a sample code using Sortable.JS, I ported it and wrote the following code. With this code, I expected there were two vertical independent scroll bars, such as in this example image:
However, there aren't, as shown in this image from running my code:
.
My code:
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/konva#5.0.2/konva.min.js"></script>
<meta charset="utf-8" />
<style>
body {
margin: 0;
padding: 0;
background-color: #f0f0f0;
height:100%;
overflow: hidden;
}
#Leftcontainer {
overflow: auto;
}
#Rightcontainer {
overflow: auto;
}
</style>
</head>
<body>
<div id="container">
<div id="Leftcontainer"></div> <!-- Left Column -->
<div id="Rightcontainer"></div> <!-- Right Column -->
</div>
<script>
var width = window.innerWidth;
var height = window.innerHeight;
var stage = new Konva.Stage({
container: 'container',
width: width,
height: 60*1090,
});
//Layer 1
var layer = new Konva.Layer();
stage.add(layer);
//Layer 2
var tempLayer = new Konva.Layer();
stage.add(tempLayer);
//Load Image (Group of Left Column)
var leftGroup = new Konva.Group({
id: 'Leftcontainer',
});
layer.add(leftGroup);
for (let i = 0; i < 100; i++) {
var imageObj = new Image();
imageObj.src = './assets/apple.jpg';
imageObj.addEventListener('load', function() {
var dragImage = new Konva.Image({
x: 5,
y: 20+(5+100)*i,
image: imageObj,
width: 100,
height: 100,
draggable: false,
});
leftGroup.add(dragImage);
layer.draw();
});
};
//Load Text (Group of Right Column)
var rightGroup = new Konva.Group({
id: 'Rightcontainer',
});
layer.add(rightGroup);
for (var i = 0; i < 100; i++) {
var WordLabel = new Konva.Label({
x: 300,
y: 18+60*i,
opacity: 0.75,
draggable: true,
fill: 'green',
});
WordLabel.add(
new Konva.Tag({
fill: 'green',
lineJoin: 'round'
})
);
WordLabel.add(
new Konva.Text({
text: "Apple",
fontFamily: 'Calibri',
fontSize: 18,
padding: 5,
fill: 'white',
})
);
rightGroup.add(WordLabel);
};
layer.draw();
</script>
</body>
</html>

It would appear that you are intending there to be two columns on your page since you have the HTML for the container, Leftcontainer and Rightcontainer. You then appear to be setting a Konva stage in the 'container' div but later creating Konva groups to co-relate to left + right containers. You then make these groups long and expect there to be vertical scrollbars.
You are making a false assumption regarding the relationship between the HTML5 canvas (for which Konva is a wrapper) and its interaction with HTML elements. The basic principle is that an HTML5 canvas 'lives' inside a single HTML element. You cannot 'share' bits of it between HTML elements in the way that you are attempting.
[Aside: Under the covers, Konva DOES create a stage per layer, but that still does not allow placing those layers into other host containers than the main stage.]
Options:
1 - you do not specifically require a canvas-based solution to provide an image-based drag & drop. You would already have found this with sortable.js. But if you are simply using this as a learning activity to understand the canvas then well done you!
2 - continuing this the canvas approach, your basic architecture of a stage and a group per column is reasonable. But you have to take care of producing the scroll bars since in the world of canvas there are no such handy UI shortcuts.
3 - again following a canvas solution, ignore the main 'container' element, and create a stage in EACH of the left and right container elements to represent your left and right columns. Draw the content, then approach the problem as one of dragging an element from one canvas to another.

Related

Scale down a Konvajs stage without losing quality

Consider having a large (2000x1000) stage with some text in it. The stage gets downscaled to 1000x500 making the text unreadable. Then we try to enlarge the text by zooming it in.
Expected: the text should become readable again at some point.
Actual: the text remains unreadable (blurred) no matter how much we zoom in.
Try zooming the page in (with native browser zoom on desktop) after running the snippet:
const stage = new Konva.Stage({
container: 'container',
width: 2000,
height: 1000,
});
const layer = new Konva.Layer();
stage.add(layer);
const rect = new Konva.Text({
x : 50, y : 50, width: 100, height: 100,
fontSize: 12,
text: "This text should be readable when the viewport gets downscaled"
});
layer.add(rect).draw();
stage.scale({x: 0.5, y: 0.5});
stage.setAttrs({width: 1000, height: 500});
stage.draw();
<script src="https://cdnjs.cloudflare.com/ajax/libs/konva/2.6.0/konva.js"></script>
<div id="container"></div>
The quality loss can be avoided by downscaling with CSS only, like this:
const stage = new Konva.Stage({
container: 'container',
width: 2000,
height: 1000,
});
const layer = new Konva.Layer();
stage.add(layer);
const rect = new Konva.Text({
x : 50, y : 50, width: 100, height: 100,
fontSize: 12,
text: "This text should be readable when the viewport gets downscaled"
});
layer.add(rect).draw();
stage.getChildren().forEach(function(layer) {
layer.canvas._canvas.style.width = "1000px";
layer.canvas._canvas.style.height = "500px";
layer.hitCanvas.setSize(1000, 500);
layer.hitCanvas.context.scale(0.5, 0.5);
});
stage.draw();
<script src="https://cdnjs.cloudflare.com/ajax/libs/konva/2.6.0/konva.js"></script>
<div id="container"></div>
Note how text becomes readable at a certain level of zooming.
The workaround breaks Konvajs abstraction. What problems it can potentially cause? Is there a better way, which uses only public methods exposed by Konvajs?
In fabric.js it can be done like this (complete example here):
canvas.setDimensions({width: '1000px', height: '500px'}, {cssOnly: true});
Konva is a canvas framework. Canvas is a bitmap image unlike vector elements like SVG. So that "blur" should be expected. Technically to fix the issue you can redraw stage with higher pixelRatio on zoom event:
Konva.pixelRatio = 4
stage.draw();
That code will generate more pixels for canvas element. But the page may be very heavy in RAM in this case because Konva will have to produce very large canvas. In most of the mobile apps, you don't need native zooming and you can use responsive design. For zooming the stage, you can use Konva methods.

How to implement a vertical scrollbar?

Does anyone know how to implement a vertical scrollbar for a vis.js timeline? I have read the visjs.org documentation, other threads here on stack overflow and on GitHub, but I still can't implement the scrollbar.
Should it be enough to write verticalScroll: true in the configuration for a vis.js timeline? This is what I have as configuration at the moment. Do I need to write it in some other way? Or do I need to implement the vertical scroll in a totally different way?
// Configuration for the Timeline
var options = {
width: '100%',
height: '100%',
minHeight: '300px',
padding: '0px',
orientation: 'top',
max: futureDate,
min: pastDate,
groupOrder: 'start',
zoomMin: '86400000',
margin: {item: {horizontal: 0, vertical: 5}, axis: 5},
verticalScroll: true,
zoomKey: 'ctrlKey'
};
A priori the options taken are good, It would be enough to reduce the height of the timeline directly in the options rather than to use "minHeight" in this case. Normally, this should bring up the scroll bar.
To check this, reduce the timeline height to 150 px in the options (e.g)
You can also generate a large number of groups so that they exceed the vertical left pannel capacity of timeline so that the vertical scrollbar displays.
UPDATED with minimal example adapted from "vis.js examples"
See also the timeline documentation on website for configuring options...
<html>
<head>
<title>Timeline | Vertical Scroll Option</title>
<!-- note: moment.js must be loaded before vis.js, else vis.js uses its embedded version of moment.js -->
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/vis/4.21.0/vis.min.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/vis/4.21.0/vis.min.css" />
</head>
<body>
<h1>Timeline vertical scroll option</h1>
<h2>With <code>
verticalScroll: true,
zoomKey: 'ctrlKey'</code>
</h2>
<div id="mytimeline1"></div>
<script>
// create groups
var numberOfGroups = 25;
var groups = new vis.DataSet()
for (var i = 0; i < numberOfGroups; i++) {
groups.add({
id: i,
content: 'Truck ' + i
})
}
// create items
var numberOfItems = 1000;
var items = new vis.DataSet();
var itemsPerGroup = Math.round(numberOfItems/numberOfGroups);
for (var truck = 0; truck < numberOfGroups; truck++) {
var date = new Date();
for (var order = 0; order < itemsPerGroup; order++) {
date.setHours(date.getHours() + 4 * (Math.random() < 0.2));
var start = new Date(date);
date.setHours(date.getHours() + 2 + Math.floor(Math.random()*4));
var end = new Date(date);
var orderIndex = order + itemsPerGroup * truck
items.add({
id: orderIndex,
group: truck,
start: start,
end: end,
content: 'Order ' + orderIndex
});
}
}
// specify options
var options = {
stack: true,
verticalScroll: true,
zoomKey: 'ctrlKey',
height: 200, // you can use also "300px"
start: new Date(),
end: new Date(1000*60*60*24 + (new Date()).valueOf()),
};
// create a Timeline
var container1 = document.getElementById('mytimeline1');
timeline1 = new vis.Timeline(container1, items, groups, options);
</script>
</body>
</html>
It sounds like you can get a scrollbar using overflow-y: scroll. Also, height: 100% will most likely never cause this to kick in (unless this element is contained within another element that has a set height) as the element that you are editing will keep growing in height rather than staying a certain height and have a scrollbar. So I would recommend removing height: 100% and using max-height instead (if your element isn't contained within another element), so if the element content grows to something larger than your max-height, it will begin to scroll. If you're looking to style that scrollbar, that's a whole different story.
https://www.w3schools.com/cssref/css3_pr_overflow-y.asp

Fabricjs Textbox make the text shrink to fit

I would define a text box (single-line)
By default, with a character size of 16 (for example)
When the text gets bigger than the text box, I do not want it to wrap or the text box gets bigger, I want the text size to fit the maximum size of the text box. text. When the text returns to a smaller size, it can resume its initial size (in our example 16). Possibly manage a minimum size
If you have an idea, I take :)
thanks in advance
Test Case : http://jsfiddle.net/Da7SP/273/
// initialize fabric canvas and assign to global windows object for debug
var canvas = window._canvas = new fabric.Canvas('c');
// ADD YOUR CODE HERE
var canvas = window._canvas = new fabric.Canvas('c');
var t1 = new fabric.Textbox('My Text', {
width: 200,
top: 5,
left: 5,
fontSize: 16,
textAlign: 'center'
});
var t2 = new fabric.Textbox('My text is longer, but I do not want the box to grow, or the text to wrap. I only want the text to fit the available size', {
width: 200,
height: 200,
top: 250,
left: 5,
fontSize: 16,
textAlign: 'center'
});
canvas.add(t1);
canvas.add(t2);
A small video to explain what I want :
When the text gets bigger than the text box, I want the text size fit the maximum size of the text box.
This is a basic fiddle that can replicate your idea.
The point is that you have an event that fires on every text change and that can be used to do something before the textbox is rendered.
In this case i m shrinking font size based on a non standard parameter i added to textbox called fixedWidth
// ADD YOUR CODE HERE
var canvas = new fabric.Canvas('c');
var t1 = new fabric.Textbox('MyText', {
width: 150,
top: 5,
left: 5,
fontSize: 16,
textAlign: 'center',
fixedWidth: 150
});
canvas.on('text:changed', function(opt) {
var t1 = opt.target;
if (t1.width > t1.fixedWidth) {
t1.fontSize *= t1.fixedWidth / (t1.width + 1);
t1.width = t1.fixedWidth;
}
});
canvas.add(t1);
canvas {
border: 1px solid #999;
}
<script src="https://rawgithub.com/kangax/fabric.js/master/dist/fabric.js"></script>
<canvas id="c" width="600" height="600"></canvas>
Here's a similar answer to https://stackoverflow.com/a/41335051/1469525 but this example modifies the fontSize up to a maxFontSize (the initial fontSize). This allows the text to shrink and then grow back to the original size, as well as preventing line wraps.
(Note, if you use this, you'll have to find a way to prevent the user from using the enter key. I put in a check that basically cancels it if an enter key is detected. My app actually has a separate form to enter the text, so I can control prevention through normal javascript. I'm not quite sure how to do that directly on a canvas element when it is editable like here...)
var canvas = new fabric.Canvas('c');
var t1 = new fabric.Textbox('MyText', {
width: 150,
top: 5,
left: 5,
fontSize: 16,
textAlign: 'center',
maxFontSize: 16,
});
canvas.on('text:changed', function(opt) {
var t1 = opt.target;
if (t1.text.match(/[\r\n]/)) return;
t1.set({
fontSize: t1.maxFontSize,
});
while (t1._textLines.length > 1) {
t1.set({
fontSize: t1.fontSize - 1,
});
}
});
canvas.add(t1);
canvas {
border: 1px solid #999;
}
<script src="https://rawgithub.com/kangax/fabric.js/master/dist/fabric.js"></script>
<canvas id="c" width="600" height="600"></canvas>

How to change first view position

I use OpenSeadragon 1.2.1.
I want to show wide image(4096 x 2160),
and change first view position.
x: 640px;
y: 320px;
width: 1024px;
height:768px;
crip(320px, 1664px, 1088px, 640px);
HTML code
<div id="mycanvas" style="width:1024px;height:768px;"></div>
<script src="./openseadragon.min.js"></script>
<script>
var viewer = OpenSeadragon(
{
id: "mycanvas",
prefixUrl: "./images/",
tileSources: "./dzc_output_images/datas.xml"
});
viewer.addHandler('open', function()
{
// I want to change first view position.
// viewer.???
// viewer.viewport.applyConstraints();
}
</script>
Use "Class:Rect / Class:DisplayRect" or other Classes ?
https://openseadragon.github.io/docs/OpenSeadragon.Rect.html
https://openseadragon.github.io/docs/OpenSeadragon.DisplayRect.html
How to use these Classes ?
Your best bet is to figure out the location you want to feature, as a rectangle in viewport coordinates (where 0 is the left side of the image and 1 is the right side). For instance (inside the "open" handler you've written):
var box = new OpenSeadragon.Rect(0.25, 0.25, 0.5, 0.5);
viewer.viewport.fitBounds(box, true);

kinetic.js change fill type of object after instantiation, after added to layer and scene, and in animation loop

I am attempting to change the fill style of a KineticJS Rect object after it has been instantiated, after being added to the layer and scene, and in the animation loop.
I am trying to toggle the fill type between a single color type to a linear gradient type based on a user button control in my main app file that renders the canvas.
I instantiate the object in a different file which is a class I wrote that instantiates a KineticJS Rect with a linear gradient fill initially in it's constructor like so:
function MyBackground(width,height,c1,c2,mode) {
this.width = width;
this.height = height;
this.color1 = c1;
this.color2 = c2;
this.mode = mode;
this.background = new Kinetic.Rect({
x: 0,
y: 0,
width: this.width,
height: this.height,
fillLinearGradientStartPoint: [0, 0],
fillLinearGradientEndPoint: [this.width, this.height],
fillLinearGradientColorStops: [0, this.color1, 1, this.color2]
});
this.getMode = function() { return this.mode; }; }
I then create an instance of this in the main file:
var myBack= new MyBackground(stageWidth,stageHeight,getRandomHexColor(),getRandomHexColor(),'dualColorLinearGradientStatic');
var background = myBack.getBackground();
myBack.setMode('singleColor');
I then add the background Rect to the layer, and the layer to the stage:
layer.add(background);
stage.add(layer);
I then start the animation loop code and after that the event handler that catches the button press to change the fill trying this inside:
myBack.setMode(bgmodeselected);
background = myBack.getBackground();
The app page loads fine showing the initial linear gradient fill. If I select that same mode of linear gradient and press the button control, it changes the colors as I desire maintaining the fill type of linear gradient. If I then switch the mode to single color fill and click the control button, that works too, changing the rect to a single color.
Here is what I have inside my class function to change the mode that makes that specifically work:
this.background.setFill(this.color1);
Inside that same function based on a conditional it should change the fill to linear gradient (and does this as long as its not changed to single color fill as above first)
this.background.setAttrs({
fillLinearGradientStartPoint: [0, 0],
fillLinearGradientEndPoint: [960, 600],
fillLinearGradientColorStops: [0, this.color1, 1, this.color2]
});
I've also tried within that same block:
this.background.setFillLinearGradientStartPoint(0, 0);
this.background.setFillLinearGradientEndPoint(960, 600);
this.background.setFillLinearGradientColorStops([0, this.color1, 1, this.color2]);
I know that the proper variables and values are being passed to the function and that the conditionals are working because it will change the fill mode type from its (as initially instantiated) linear gradient fill to a single color fill (which will continue working even as a different single color).
The problem is when I try to switch BACK to linear gradient fill it will not do so, or repaint/refresh at least despite it's calling this same function with the proper values. So, I suppose that my specific question is how can I change the fill style of a KineticJS Rect multiple times, from a single color fill back to a linear gradient fill after it has been already been added to the layer and stage and has an animation loop implemented?
This is my first question post so I hope that I am doing so properly; please inform me if I should be doing anything differently. Thanks.
Welcome to stackoverflow!
When you’re re-applying your gradient, be sure to clear out the solid color fill:
// clear the solid fill
this.setFill('');
// then apply the gradient fill
this.setFillLinearGradientStartPoint(-50);
this.setFillLinearGradientEndPoint(50);
this.setFillLinearGradientColorStops([0, 'green', 1, 'yellow']);
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/dmMF2/
<!DOCTYPE HTML>
<html>
<head>
<style>
body {
margin: 0px;
padding: 0px;
}
</style>
</head>
<body>
<div id="container"></div>
<script src="http://www.html5canvastutorials.com/libraries/kinetic-v4.3.0-beta2.js"></script>
<script>
function draw(images) {
var stage = new Kinetic.Stage({
container: 'container',
width: 578,
height: 200
});
var layer = new Kinetic.Layer();
var linearGradPentagon = new Kinetic.Rect({
x: 100,
y: 30,
width: 75,
height: 50,
fill:"red",
stroke: 'black',
strokeWidth: 4,
draggable: true
});
linearGradPentagon.on('mouseover touchstart', function() {
this.setFill('');
this.setFillLinearGradientStartPoint(-50);
this.setFillLinearGradientEndPoint(50);
this.setFillLinearGradientColorStops([0, 'green', 1, 'yellow']);
layer.draw();
});
linearGradPentagon.on('mouseout touchend', function() {
this.setFill('red');
layer.draw();
});
layer.add(linearGradPentagon);
stage.add(layer);
}
draw();
</script>
</body>
</html>

Categories