The behaviour is: snaps to all other draggable elements
I did as much as I could but I couldn't justify it at the intersection. does not pass to the other while applying any snap.
I want to do the snap event with the possibilities given by interact.
I have created a fiddle here: Fiddle Link
This is my JS Code:
var element = document.getElementById('draggable-element')
var x = 0; var y = 0
var targets = [
//{ x : 500, y : 350 },
//{ x : 500, y : 410 },
//{ x : 620, y : 350 },
//{ x : 620, y : 410 },
{ x : 500 },
{ x : 620 },
{ y : 350 },
{ y : 410 }
];
const mySnap = interact.modifiers.snap({
targets: targets,
range: 20,
relativePoints: [
{ x: 0 , y: 0 }, // snap relative to the element's top-left,
//{ x: 0.5, y: 0.5 }, // to the center
{ x: 1 , y: 1 } // and to the bottom-right
]
});
interact(element)
.draggable({
modifiers: [
mySnap
],
inertia: false
})
.on('dragmove', function (event) {
var target = event.target
// keep the dragged position in the data-x/data-y attributes
var x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx
var y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy
// translate the element
target.style.webkitTransform =
target.style.transform =
'translate(' + x + 'px, ' + y + 'px)'
// update the posiion attributes
target.setAttribute('data-x', x)
target.setAttribute('data-y', y)
})
.resizable({
// resize from all edges and corners
edges: { left: true, right: true, bottom: true, top: true },
modifiers: [
mySnap
],
inertia: false
})
.on('resizemove', function (event) {
var target = event.target
var x = (parseFloat(target.getAttribute('data-x')) || 0)
var y = (parseFloat(target.getAttribute('data-y')) || 0)
// update the element's style
target.style.width = event.rect.width + 'px'
target.style.height = event.rect.height + 'px'
// translate when resizing from top or left edges
x += event.deltaRect.left
y += event.deltaRect.top
target.style.webkitTransform = target.style.transform =
'translate(' + x + 'px,' + y + 'px)'
target.setAttribute('data-x', x)
target.setAttribute('data-y', y)
target.textContent = Math.round(event.rect.width) + '\u00D7' + Math.round(event.rect.height)
})
* {
margin: 0;
padding: 0;
}
#draggable-element {
width: 72px;
height: 72px;
background: yellowgreen;
position: absolute;
left: 0px;
top: 0px;
touch-action: none;
}
#guide-box {
width: 120px;
height: 60px;
background: wheat;
position: absolute;
left: 500px;
top: 350px;
}
.line-x{
position: absolute;
height: 1px;
width: 100%;
background-color: tomato;
}
.line-y{
position: absolute;
width: 1px;
height: 100%;
background-color: teal;
}
.line-x.xT-line{ top: 350px; }
.line-x.xB-line{ top: 410px; }
.line-y.yL-line{ left: 500px; }
.line-y.yR-line{ left: 620px; }
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="guide-box"></div>
<div id="draggable-element"></div>
<div class="xT-line line-x"></div>
<div class="xB-line line-x"></div>
<div class="yL-line line-y"></div>
<div class="yR-line line-y"></div>
<script src="https://cdn.jsdelivr.net/npm/interactjs/dist/interact.min.js"></script>
<script src="script.js"></script>
</body>
</html>
Related
I have a small program that when you press down on ArrowDown, the circle should move downwards from its position on screen. The issue I have is that my initial positioning for my svg circle object set by cx: 5% and cy:5% are different than my 5 * xshift and 5 * yshift which are derived from document.getElementById("test").clientWidth/100 and document.getElementById("test").clientHeight/100 which I would believe to be a good conversion to percentage. However when the program is run, the initial ArrowDown corrects the initial placement and creates a noticable shifted difference.
var goal = {x: window.innerWidth, y: window.innerHeight}
var xshift = document.getElementById("test").clientWidth/100
var yshift = document.getElementById("test").clientHeight/100
var player= {type:"Player", x:5 * xshift, y: 5 * yshift}
document.addEventListener('keydown', function(event) {
if (event.key === 'ArrowDown'){
player.y += yshift;
if(player.y > (100 * yshift) -(9 * yshift)){
player.y = 91 * yshift;
}
//document.getElementById("UI").innerHTML = "Player position is x: " +player.x + " y: " + player.y;
document.getElementById('circe').setAttribute("transform", 'translate('+player.x+","+player.y+")");
}
}, true);
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Basic Snake Game</title>
<style>
html, body {
margin: 0;
padding: 0;
border: 0;
height: 100%
}
svg {
position: absolute;
margin: 0;
padding: 0;
border: 0;
background-color: blueviolet;
}
</style>
</head>
<body>
<!-- <h1 id="UI">Player position is x: 0 y: 0</h1> -->
<svg id="test" width="100%" height="100%">
<circle id="circe" cx="5%" cy="5%" r="5%" stroke="green" stroke-width="0%" fill="yellow" transform="translate(0,0)" />
</svg>
<script src="test.js"></script>
</body>
</html>
You can check complete code from: this fiddle, I combined your pieces of code and added a single line.
`
var goal = {x: window.innerWidth, y: window.innerHeight}
var xshift = document.getElementById("test").clientWidth/100
var yshift = document.getElementById("test").clientHeight/100
var player= {type:"Player", x:5 * xshift, y: 5 * yshift}
document.addEventListener('keydown', function(event) {
if (event.key === 'ArrowDown'){
doShift();
}
}, true);
function doShift(){
player.y += yshift;
if(player.y > (100 * yshift) -(9 * yshift)){
player.y = 91 * yshift;
}
//document.getElementById("UI").innerHTML = "Player position is x: " +player.x + " y: " + player.y;
document.getElementById('circe').setAttribute("transform", 'translate('+player.x+","+player.y+")");
}
//document.addEventListener("onready", ()=> doShift());
//document.ondomcontentready=doShift;
doShift();
`;
Although it looks like a hack, the final doShift(); call creates the same effect as when you first press the down button - you specify what you want.
They are different because you are using a transform. The transform is being applied on top of (in addition to) the cx and cy coordinates.
In other words, the circle is being positioned at:
x = cx + translate.x = 5% + 5%
y = cy + (n * translate.y) = 5% + (n * 5%)
One way to fix this is to forget the transform attribute and just update the cx and cy attributes instead. Eg.
document.getElementById('circe').setAttribute("cy", player.y);
Demo
var goal = {x: window.innerWidth, y: window.innerHeight}
var xshift = document.getElementById("test").clientWidth/100
var yshift = document.getElementById("test").clientHeight/100
var player= {type:"Player", x:5 * xshift, y: 5 * yshift}
document.addEventListener('keydown', function(event) {
if (event.key === 'ArrowDown'){
player.y += yshift;
if(player.y > (100 * yshift) -(9 * yshift)){
player.y = 91 * yshift;
}
//document.getElementById("UI").innerHTML = "Player position is x: " +player.x + " y: " + player.y;
document.getElementById('circe').setAttribute("cy", player.y);
}
}, true);
html, body {
margin: 0;
padding: 0;
border: 0;
height: 100%;
}
svg {
position: absolute;
margin: 0;
padding: 0;
border: 0;
background-color: blueviolet;
}
<!-- <h1 id="UI">Player position is x: 0 y: 0</h1> -->
<svg id="test" width="100%" height="100%">
<circle id="circe" cx="5%" cy="5%" r="5%" stroke="green" stroke-width="0%" fill="yellow" transform="translate(0,0)" />
</svg>
I used the blowup.js plugin as a base, and I am trying to rotate the image, and the lens to follow the rotation. But it is not working.
When I put the rotate(180deg) for example, the lens and the image are mismatched, if I remove the rotate(180deg) they are aligned.
Anybody know how to help me?
sample in JSFiddle
var $element = $('#target');
$element.css({
'transform': 'rotate(180deg)'
}); // rotate imagem in html
// Constants
var $IMAGE_URL = $element.attr("src");
var NATIVE_IMG = new Image();
NATIVE_IMG.src = $element.attr("src");
var lens = document.createElement("div");
lens.id = "BlowupLens";
$("body").append(lens);
$blowupLens = $("#BlowupLens");
$blowupLens.css({
"position": "absolute",
"display": "none",
"pointer-events": "none",
"zIndex": 999999,
"width": 200,
"height": 200,
"border": "6px solid #FFF",
"background": "#FFF",
"border-radius": "50%",
"box-shadow": "0 8px 17px 0 rgba(0, 0, 0, 0.2)",
"background-repeat": "no-repeat",
});
// Show magnification lens
$element.mouseenter(function() {
$blowupLens.css("display", "block");
});
// Mouse motion on image
$element.mousemove(function(e) {
// Lens position coordinates
var lensX = e.pageX - (200 / 2);
var lensY = e.pageY - (200 / 2);
var width = $element.width();
var height = $element.height();
// Relative coordinates of image
var relX = e.pageX - $('#target').offset().left;
var relY = e.pageY - $('#target').offset().top;
// Zoomed image coordinates
var zoomX = -Math.floor(relX / width * (NATIVE_IMG.width) - 200 / 2);
var zoomY = -Math.floor(relY / height * (NATIVE_IMG.height) - 200 / 2);
var backPos = zoomX + "px " + zoomY + "px";
var backgroundSize = NATIVE_IMG.width + "px " + NATIVE_IMG.height + "px";
// Apply styles to lens
$blowupLens.css({
left: lensX,
top: lensY,
"background-image": "url(" + encodeURI($IMAGE_URL) + ")",
"background-size": backgroundSize,
"background-position": backPos,
"transform": "rotate(180deg)" //rotate the image original
});
})
// Hide magnification lens
$element.mouseleave(function() {
$blowupLens.css("display", "none");
});
#target {
margin-left: 160px;
width: 700;
height: 500px;
}
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>
<body>
<img id="target" src="https://iili.io/0hL7ou.png">
</body>
You might rotate the lens in the opposite direction (-180deg) and inverse background position too:
Side note: you don't want to apply background-image on every mousemove, move it to the lens init.
var $element = $('#target');
$element.css({
'transform': 'rotate(180deg)'
}); // rotate imagem in html
// Constants
var $IMAGE_URL = $element.attr("src");
var NATIVE_IMG = new Image();
NATIVE_IMG.src = $element.attr("src");
var lens = document.createElement("div");
lens.id = "BlowupLens";
$("body").append(lens);
$blowupLens = $("#BlowupLens");
$blowupLens.css({
"position": "absolute",
"display": "none",
"pointer-events": "none",
"zIndex": 999999,
"width": 200,
"height": 200,
"border": "6px solid #FFF",
"background": "#FFF",
"border-radius": "50%",
"box-shadow": "0 8px 17px 0 rgba(0, 0, 0, 0.2)",
"background-repeat": "no-repeat",
});
// Show magnification lens
$element.mouseenter(function() {
$blowupLens.css("display", "block");
});
// Mouse motion on image
$element.mousemove(function(e) {
// Lens position coordinates
var lensX = e.pageX - (200 / 2);
var lensY = e.pageY - (200 / 2);
var width = $element.width();
var height = $element.height();
// Relative coordinates of image
var relX = e.pageX - $('#target').offset().left;
var relY = e.pageY - $('#target').offset().top;
// Zoomed image coordinates
var zoomX = -Math.floor(relX / width * (NATIVE_IMG.width) - 200 / 2);
var zoomY = -Math.floor(relY / height * (NATIVE_IMG.height) - 200 / 2);
var backPos = "calc(100% - " + zoomX + "px) calc(100% - " + zoomY + "px)";
var backgroundSize = NATIVE_IMG.width + "px " + NATIVE_IMG.height + "px";
// Apply styles to lens
$blowupLens.css({
left: lensX,
top: lensY,
"background-image": "url(" + encodeURI($IMAGE_URL) + ")",
"background-size": backgroundSize,
"background-position": backPos,
"transform": "rotate(-180deg)" //rotate the image original
});
})
// Hide magnification lens
$element.mouseleave(function() {
$blowupLens.css("display", "none");
});
#target {
margin-left: 160px;
width: 400;
height: 250px;
}
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>
<body>
<img id="target" src="https://iili.io/0hL7ou.png">
</body>
Update:
For more general case of image rotation, I'd use an additional absolutely positioned image within the lens instead of background-image. It's way easier to control:
var $pic = $('#target');
// Constants
const lensD = 70, // lens diameter
src = $pic.attr("src"),
tоp = $pic.offset().top,
lеft = $pic.offset().left;
let angle, scaleX, scaleY;
const $lens = $('<div id="lens"/>')
.css({
width: lensD,
height: lensD
})
.appendTo('body');
const $lensImage = $(`<img src="${src}">`)
.on('load', function() {
scaleX = this.width / ($pic.width() || 1);
scaleY = this.height / ($pic.height() || 1);
})
.appendTo($lens);
// Mouse motion on image
$pic.mousemove(function(e) {
// Lens position
$lens.css({
transform: (`
translateX(${e.pageX - lensD/2}px)
translateY(${e.pageY - lensD/2}px)
`)
});
// Zoomed image position
$lensImage.css({
transform: (`
translateX(${(lеft - e.pageX) * scaleX + lensD/2}px)
translateY(${(tоp - e.pageY) * scaleY + lensD/2}px)
rotateZ(${angle}deg)
`)
});
});
// Show magnification lens
$pic.mouseenter(function() {
$lens.css("display", "block");
});
// Hide magnification lens
$pic.mouseleave(function() {
$lens.css("display", "none");
});
// Rotation (aux)
$('#angle')
.on('input', function() {
$('#a').val((angle = $(this).val()) + 'deg');
$pic.css({
transform: `rotateZ(${angle}deg)`
});
$lensImage.css({
transform: `rotateZ(${angle}deg)`
});
})
.trigger('input')
.val();
#a {
border: 0
}
#target,
#lens img {
width: 200px;
height: 100px;
margin: 40px;
}
#lens {
position: absolute;
top: 0;
left: 0;
z-index: 1;
display: none;
margin: 0;
padding: 0;
border: solid 3px #0003;
border-radius: 50%;
overflow: hidden;
pointer-events: none;
}
#lens img {
position: absolute;
width: auto;
height: auto;
margin: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<fieldset>
Rotate: <input id="a" readonly><br>
-180 <input type="range" min="-180" max="180" step="1" value="-9" id="angle"> 180
</fieldset>
<img id="target" src="https://picsum.photos/id/111/400/200">
I have a class, which basically draws a 16x30 grid.
Ideally, I would like to be able to execute:
OSD.setCursor(x y);
OSD.print('Text');
and have it position the word Text at position x, y with each character of Text positioned in the correct location in the grid.
Here's what I have so far:
class MAX7456 {
constructor() {
this.items = null;
this.divs = null;
}
begin() {
var ratioH = 16,
ratioW = 30;
var parent = $('<div />', {
class: 'grid',
width: ratioW * 25,
height: ratioH * 18
}).addClass('grid').appendTo('body');
for (var i = 0; i < ratioH; i++) {
for(var p = 0; p < ratioW; p++) {
this.divs = $('<div />', {
width: 25 - 1,
height: 18 - 1
}).appendTo(parent);
this.items = $('<span />', {
width: 25 - 1,
height: 18 - 1,
style: "padding-left: 2px;"
}).appendTo(this.divs);
}
}
}
setCursor(x, y) {
$('div > span:nth-child(2n-1)').text(function (i, txt) {
$(this).append(i)
i++;
//console.log(txt + $(this).next().text());
});
}
print (txt) {
}
}
var OSD = new MAX7456();
OSD.begin(); // create grid
OSD.setCursor(0, 0); // set text at cursor (x, y)
OSD.print("Label 2");
body {
padding: 0;
font-size: 12px;
}
.grid {
border: 1px solid #ccc;
border-width: 1px 0 0 1px;
}
.grid div {
border: 1px solid #ccc;
border-width: 0 1px 1px 0;
float: left;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
JSFiddle
Now this may seems not the ideal solution looking for, In that case my apologies. My requirement was to create a tiled grid based on an image so this how i managed to do it.
Total tile count can be vary as you need. (My case 2500 tiles)
When you adjust the image size that will determine what size of the tile can be.
(function($) {
var imagePadding = 0;
var pluginName = "tiles",
defaults = {
x: 2, // tiles in x axis
y: 2, // tiles in y axis
gap: {
x: 1,
y: 1
}
};
function Plugin(elem, options) {
options = $.extend({}, defaults, options);
var $elem = $(elem).wrap("<div class='tiles-wrapper' />"),
width = $elem.outerWidth(),
height = $elem.outerHeight(),
n_tiles = options.x * options.y,
tiles = [];
$elem.parent(".tiles-wrapper").css({
position: "relative",
width: width,
height: height
});
for (var $i = 0; $i < n_tiles; $i++) {
if ($i >= imagePadding) {
tiles.push("<div class='tile' data-id='" + $i + "' data-clipboard-text='" + $i + "'>" + $i + "</div>");
} else {
tiles.push("<div class='tile' data-id='" + $i + "' data-clipboard-text='" + $i + "'></div>");
}
}
var $tiles = $(tiles.join(""));
// Hide original image and insert tiles in DOM
$elem.hide().after($tiles);
// Set backgrounds
$tiles.css({
float: "left",
width: (width / options.x) - (options.gap.x || options.gap),
height: (height / options.y) - (options.gap.y || options.gap),
marginRight: options.gap.x || options.gap,
marginBottom: options.gap.y || options.gap,
backgroundImage: "url(" + $elem[0].src + ")",
lineHeight: (height / options.y) - (options.gap.y || options.gap) + "px",
textAlign: "center"
});
// Adjust position
$tiles.each(function() {
var pos = $(this).position();
this.style.backgroundPosition = -pos.left + "px " + -pos.top + "px";
});
}
$.fn[pluginName] = function(options) {
return this.each(function() {
new Plugin(this, options);
});
};
}(jQuery));
window.onload = function() {
$('#img').tiles({
x: 21.909,
y: 21.909
});
$(".tile").click(function() {
console.log($(this).data("id"));
});
};
.tiles-wrapper {
z-index: 999;
}
.tile:hover {
opacity: .80;
filter: alpha(opacity=80);
background: #fecd1f!important;
}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<!-- Latest compiled and minified JavaScript -->
<script type="text/javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<div class="container">
<div class="row">
<div class="col-md-8">
<div class="banner-head"></div>
<div class="row">
<div class="col-md-12">
<div class="image-holder">
<img id="img" src="data:image/gif;base64,R0lGODlh6ANsAwAAACH5BAAAAAAALAAAAADoA2wDhwAAAAAAMwAAZgAAmQAAzAAA/wArAAArMwArZgArmQArzAAr/wBVAABVMwBVZgBVmQBVzABV/wCAAACAMwCAZgCAmQCAzACA/wCqAACqMwCqZgCqmQCqzACq/wDVAADVMwDVZgDVmQDVzADV/wD/AAD/MwD/ZgD/mQD/zAD//zMAADMAMzMAZjMAmTMAzDMA/zMrADMrMzMrZjMrmTMrzDMr/zNVADNVMzNVZjNVmTNVzDNV/zOAADOAMzOAZjOAmTOAzDOA/zOqADOqMzOqZjOqmTOqzDOq/zPVADPVMzPVZjPVmTPVzDPV/zP/ADP/MzP/ZjP/mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2YrAGYrM2YrZmYrmWYrzGYr/2ZVAGZVM2ZVZmZVmWZVzGZV/2aAAGaAM2aAZmaAmWaAzGaA/2aqAGaqM2aqZmaqmWaqzGaq/2bVAGbVM2bVZmbVmWbVzGbV/2b/AGb/M2b/Zmb/mWb/zGb//5kAAJkAM5kAZpkAmZkAzJkA/5krAJkrM5krZpkrmZkrzJkr/5lVAJlVM5lVZplVmZlVzJlV/5mAAJmAM5mAZpmAmZmAzJmA/5mqAJmqM5mqZpmqmZmqzJmq/5nVAJnVM5nVZpnVmZnVzJnV/5n/AJn/M5n/Zpn/mZn/zJn//8wAAMwAM8wAZswAmcwAzMwA/8wrAMwrM8wrZswrmcwrzMwr/8xVAMxVM8xVZsxVmcxVzMxV/8yAAMyAM8yAZsyAmcyAzMyA/8yqAMyqM8yqZsyqmcyqzMyq/8zVAMzVM8zVZszVmczVzMzV/8z/AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8Amf8AzP8A//8rAP8rM/8rZv8rmf8rzP8r//9VAP9VM/9VZv9Vmf9VzP9V//+AAP+AM/+AZv+Amf+AzP+A//+qAP+qM/+qZv+qmf+qzP+q///VAP/VM//VZv/Vmf/VzP/V////AP//M///Zv//mf//zP///wAAAAAAAAAAAAAAAAiuAPcJHEiwoMGDCBMqXMiwocOHECNKnEixosWLGDNq3Mixo8ePIEOKHEmypMmTKFOqXMmypcuXMGPKnEmzps2bOHPq3Mmzp8+fQIMKHUq0qNGjSJMqXcq0qdOnUKNKnUq1qtWrWLNq3cq1q9evYMOKHUu2rNmzaNOqXcu2rdu3cOPKnUu3rt27ePPq3cu3r9+/gAMLHky4sOHDiBMrXsy4sePHkCNLnky5suXLmDNrSN7MubPnz6BDix5NurTp06hTq17NurXr17Bjy55Nu7bt27hz697Nu7fv38CDCx9OvLjx48iTK1/OvLnz59CjS59Ovbr169izaznfzr279+/gw4sfT768+fPo06tfz769+/fw48ufT7++/fv48+vfz7+///8ABijggAQWaOCBCCao4II0DDbo4IMQRijhhBRWaOGFGGao4YYcdujhhyCGKOKIJJZo4okopqjiiiy26OKLMMYo44w01i1o44045qjjjjz26OOPQAYp5JBEFmnkkUgmqeSSTDbp5JNQRinllFRWaeWVWGYpqeWWXHbp5ZdghinmmGSWaeaZaKap5ppstunmm3DGKeecdNZp55145qkn55589unnn4AGKuighBZq6KGIJqrooow26uijkEYq6aSUVmrppZhmI6rpppx26umnoIYq6qiklmrqqaimquqqrLbq6quwxirrrLTWImrrrbjmquuuvPbq66/ABivssMQWa+yxyCar7LLMNuvss9AfRivttNRWa+212Gar7bbcduvtt+CGK+645JZr7rnoph6r7rrstuvuu/DGK++89NZr77345qvvvvz26++/AAcdLPDABBds8MEIJ6zwwgw37PDDEEcs8cQUV2zxxRgbZ6zxxhx37PHHIIcs8sgkl2zyySinrPLKLLfsG/LLMMcs88w012zzzTjnrPPOPPfs889ABy300BlEF2300UgnrfTSTDft9NNQRy311FRXbfXVGVhnrfXWXHft9ddghy322GSXbfbZaKet9toXbLft9ttwxy333HTXbffdeOet99589+0X99+ABy744IQXbvjhiCeu+OKMN+7445AWRy755JRXbvnlmGeu+eacd+7556CHLhf66KSXbvrpqKeu+uqst+7667DHLvvstBXXbvvtuOeu++689+7778AHL/zwxBcVb/zxyCev/PLMN+/889BHL/301FdvFP312Gev/fbcd+/99+CHL/745JdvFP756Kev/vrst+/++/DHL//89NdvFf/9+Oev//789+///wAMoAAHSMACGhXwgAhMoAIXyMAGOvCBEIygBCdIwQoUWvCCGMygBjfIwQ568IMgDKEIR0gVwhKa8IQoTKEKV8jCFrrwhTCMoQxnE0jDGtrwhjjMoQ53yMMe+vCHQAwVohCHSMQiGvGISEyiEpfIxCY68YlQE4yiFKdIxSpa8YpYzKIWt8jFLnoS8YtgDKMYx0jGMprxjGhMoxrXE8jGNrrxjXCMoxznSMc62vGOeMwSox73yMc++vGPgAykIAdJyEIaE/KQiEykIhfJyEY68pGQjKQkJ0kSyUpa8pKYzKQmN8nJTnryk6AMEaUoR0nKUprylKhMpSpXycpWErrylbCMpSxnScta2vKWuMylLhB3ycte+vKXwAymMIdJzGIaEvOYyEymMpfJzGY685nQjKY0pxFJzWpa85rYzKY2t8nNbnrzmxDgDKc4x0nOcprznOhMpzrXEcnOdrrznfCMpzznSc962vOeEPjMpz73yc9++vOfAA2oQAcPStCCGvSgCE2oQhfK0IY6EfShEI2oRCdK0Ypa9KIYzahGDzfK0Y569KMgDalIR0rSkg+a9KQoTalKV8rSlrr0pTAPjalMZ0rTmtr0pjjNqU53D8rTnvr0p0ANqlCHStSiGg/1qEhNqlKXytSmOvWpUI0PqlSnStWqWvWqWM2qVrfKENWuevWrYA2rWMdK1rKa9awNaE2rWtfK1ra69a1wjQ+rXOdK17ra9a54zate98oO17769a+ADaxgB0vYwhoP9rCITaxiF8vYxjr2sZCNDqxkJ0vZylr2spjNrGY3DsvZznr2s6ANrWhHS9rSDpr2tKhNrWpXy9rWuva1DbCNrWxnS9va2va2uM0OrW53y9ve+va3wA2ucIcOS9ziGve4yE2ucpfL3OYMOve50I2udKdL3epaDve62M2udrfL3e5697vgDg2veMdL3vKa97zoTa96DNfL3va6973wja985wxL3/ra9774za9+98sO3/76978ADrCAB0zgAhsN+MAITrCCF8zgBjv4wQwQjrCEJ0zhClv4whgOzrCGN8zhDnv4wyAOsYgQR0ziEpv4xChOsYpX7JqAAAA7"
alt="event picture" />
</div>
</div>
</div>
</div>
</div>
</div>
Good day people!
I have run into an issue with my simple single day calendar script.
I've been tasked to create a single day calendar, which shows each block hour from 9am to 6pm. If an event overlaps another, they should equal the same width and not overlap. I have managed to achieve this for two events, however if more than two overlap, things go abit south, I need help figuring out a method to fix this where any number of events overlap, their widths will equal the same.
Events are rendered on the calendar using a global function:
renderDay([{start: 30, end: 120},{start: 60, end: 120}])
which takes an array of objects as an argument, where the integers are the number of minutes pasted from 9am. eg. 30 is 9:30am, 120 is 11am
here is the collision function I took from stackoverflow
// collision function to return boolean
// attribute: http://stackoverflow.com/questions/14012766/detecting-whether-two-divs-overlap
function collision($div1, $div2) {
let x1 = $div1.offset().left;
let y1 = $div1.offset().top;
let h1 = $div1.outerHeight(true);
let w1 = $div1.outerWidth(true);
let b1 = y1 + h1;
let r1 = x1 + w1;
let x2 = $div2.offset().left;
let y2 = $div2.offset().top;
let h2 = $div2.outerHeight(true);
let w2 = $div2.outerWidth(true);
let b2 = y2 + h2;
let r2 = x2 + w2;
if (b1 < y2 || y1 > b2 || r1 < x2 || x1 > r2) return false;
return true;
}
I run a loop on all the event divs which I want to check for overlaps
// JQuery on each, check if collision
$('.event').each(function(index, value) {
// statement to break out on final loop
if(index === $('.event').length - 1) return;
console.log('at index: ', index);
// if collison === true, halve width of both event divs, re-position
if(collision( $('#e-'+index) , $('#e-'+(index + 1)) )) {
$('#e-'+index).css('width', $('#e-'+index).width() / 2);
$('#e-'+(index+ 1)).css('width', $('#e-'+(index+ 1)).width() / 2).css('left', $('#e-'+(index + 1)).width());
if(collision)
}
})
}
})
Screenshots to help visualize :)
When two overlap, they have equal widths
When three or more overlap, things go wrong
Any help would be greatly appreciated!
DW
After looking at the code, it seems overly complex to check the rendered elements for collision when you can work this out from the start and end times.
The way I've done it, is to group events which collide in arrays like so:
let collisions = [
// only 1 event in this array so no collisions
[{
start: 30,
end: 120
}],
// 3 events in this array which have overlapping times
[{
start: 300,
end: 330
}, {
start: 290,
end: 330
}, {
start: 300,
end: 330
}]
];
Then we iterate through each group of collisions, and create the elements with the appropriate width and positioning.
for (var i = 0; i < collisions.length; i++) {
var collision = collisions[i];
for (var j = 0; j < collision.length; j++) {
var event = collision[j];
let height = event.end - event.start;
let top = event.start + 50;
// 360 = max width of event
let width = 360 / collision.length;
// a lot of this could be moved into a css class
// I removed the "display: inline-block" code because these are absolutely positioned. Replaced it with "left: (j * width)px"
let div = $(`<div id=${'e-'+ (i + j)}>`).css('position', 'absolute').css('top', top)
.css('height', height).css('width', width).css('left', (j * width) + 'px')
.css('backgroundColor', arrayOfColors.shift()).addClass('event')
.text('New Event').css('fontWeight', 'bold');
// append event div to parent container
$('#events').append(div);
}
}
//**********************************************************************
//
// TITLE - Thought Machine Coding Challenge, Single Day Calendar
// AUTHOR - DOUGLAS WISSETT WALKER
// DATE - 21/04/2016
// VERSION - 0.0.3
// PREVIOUS - 0.0.2
//
//**********************************************************************
let arr = [{
start: 30,
end: 120
}, {
start: 70,
end: 180
}, {
start: 80,
end: 190
}, {
start: 300,
end: 330
}, {
start: 290,
end: 330
}, {
start: 220,
end: 260
}, {
start: 220,
end: 260
}, {
start: 220,
end: 260
}, {
start: 220,
end: 260
}, {
start: 400,
end: 440
}, {
start: 20,
end: 200
}];
let renderDay;
$(document).ready(() => {
renderDay = function(array) {
$('.event').each(function(i, el) {
$(el).remove();
});
// background colors for events
let arrayOfColors = [
'rgba(255, 153, 153, 0.75)',
'rgba(255, 204, 153, 0.75)',
'rgba(204, 255, 153, 0.75)',
'rgba(153, 255, 255, 0.75)',
'rgba(153, 153, 255, 0.75)',
'rgba(255, 153, 255, 0.75)'
]
let collisions = mapCollisions(array);
let eventCount = 0; // used for unique id
for (let i = 0; i < collisions.length; i++) {
let collision = collisions[i];
for (let j = 0; j < collision.length; j++) {
let event = collision[j];
let height = event.end - event.start;
let top = event.start + 50;
// 360 = max width of event
let width = 360 / collision.length;
// a lot of this could be moved into a css class
// I removed the "display: inline-block" code because these are absolutely positioned
// Replaced it with "left: (j * width)px"
let div = $("<div id='e-" + eventCount + "'>").css('position', 'absolute').css('top', top)
.css('height', height).css('width', width).css('left', (j * width) + 'px')
.css('backgroundColor', arrayOfColors.shift()).addClass('event')
.text('New Event').css('fontWeight', 'bold');
eventCount++;
// append event div to parent container
$('#events').append(div);
}
}
}
renderDay(arr);
});
// Sorry this is pretty messy and I'm not familiar with ES6/Typescript or whatever you are using
function mapCollisions(array) {
let collisions = [];
for (let i = 0; i < array.length; i++) {
let event = array[i];
let collides = false;
// for each group of colliding events, check if this event collides
for (let j = 0; j < collisions.length; j++) {
let collision = collisions[j];
// for each event in a group of colliding events
for (let k = 0; k < collision.length; k++) {
let collidingEvent = collision[k]; // event which possibly collides
// Not 100% sure if this will catch all collisions
if (
event.start >= collidingEvent.start && event.start < collidingEvent.end || event.end <= collidingEvent.end && event.end > collidingEvent.start || collidingEvent.start >= event.start && collidingEvent.start < event.end || collidingEvent.end <= event.end && collidingEvent.end > event.start) {
collision.push(event);
collides = true;
break;
}
}
}
if (!collides) {
collisions.push([event]);
}
}
console.log(collisions);
return collisions;
}
html,
body {
margin: 0;
padding: 0;
font-family: sans-serif;
}
#container {
height: 100%;
width: 100%;
}
#header-title {
text-align: center;
}
#calendar {
width: 400px;
height: 620px;
margin-top: 70px;
}
#events {
position: absolute;
top: 80px;
left: 100px;
width: 800px;
height: 620px;
}
.event {
box-shadow: 0 0 20px black;
border-radius: 5px;
}
.hr-block {
border-top: 2px solid black;
height: 58px;
margin: 0;
padding: 0;
margin-left: 100px;
min-width: 360px;
opacity: .5;
}
.hr-header {
position: relative;
top: -33px;
left: -68px;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css/styles.css">
<link rel="stylesheet" href="css/responsive.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.3/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script charset="UTF-8" src="js/moment.js"></script>
<script charset="UTF-8" src="js/script2.js"></script>
<title>Thought Machine Code Challenge</title>
</head>
<body>
<div id="container">
<div class="header">
<h1 id="header-title"></h1>
</div>
<div id="calendar">
<div class="hr-block">
<h2 class="hr-header">09:00</h2>
</div>
<div class="hr-block">
<h2 class="hr-header">10:00</h2>
</div>
<div class="hr-block">
<h2 class="hr-header">11:00</h2>
</div>
<div class="hr-block">
<h2 class="hr-header">12:00</h2>
</div>
<div class="hr-block">
<h2 class="hr-header">13:00</h2>
</div>
<div class="hr-block">
<h2 class="hr-header">14:00</h2>
</div>
<div class="hr-block">
<h2 class="hr-header">15:00</h2>
</div>
<div class="hr-block">
<h2 class="hr-header">16:00</h2>
</div>
<div class="hr-block">
<h2 class="hr-header">17:00</h2>
</div>
<div class="hr-block">
<h2 class="hr-header">18:00</h2>
</div>
</div>
</div>
<div id="events">
</div>
<script>
document.getElementById("header-title").innerHTML = moment().calendar();
</script>
</body>
</html>
working index, script and css files
//**********************************************************************
//
// TITLE - Thought Machine Coding Challenge, Single Day Calendar
// AUTHOR - DOUGLAS WISSETT WALKER
// DATE - 21/04/2016
// VERSION - 0.0.3
// PREVIOUS - 0.0.2
//
//**********************************************************************
let arr = [{start: 30, end: 120},{start: 300, end: 330},{start: 290, end: 330}];
let renderDay;
$(document).ready(() => {
renderDay = function(array) {
$('.event').each(function(i, el) {
$(el).remove();
});
// background colors for events
let arrayOfColors = [
'rgba(255, 153, 153, 0.75)',
'rgba(255, 204, 153, 0.75)',
'rgba(204, 255, 153, 0.75)',
'rgba(153, 255, 255, 0.75)',
'rgba(153, 153, 255, 0.75)',
'rgba(255, 153, 255, 0.75)'
]
// iterate through each event time
array.forEach((eventTimes, index) => {
// define event height and top position on calendar
let height = eventTimes.end - eventTimes.start;
let top = eventTimes.start + 50;
// max width of event
let width = 360;
// create event div
let div = $(`<div id=${'e-'+index}>`).css('position', 'absolute').css('top', top)
.css('height', height).css('width', width).css('display', 'inline-block')
.css('backgroundColor', arrayOfColors.shift()).addClass('event')
.text('New Event').css('fontWeight', 'bold');
// append event div to parent container
$('#events').append(div);
})
// JQuery on each, check if collision
$('.event').each(function(index, value) {
// statement to break out on final loop
if(index === $('.event').length - 1) return;
console.log('at index: ', index);
// if collison === true, halve width of both event divs, re-position
if(collision( $('#e-'+index) , $('#e-'+(index + 1)) )) {
$('#e-'+index).css('width', $('#e-'+index).width() / 2);
$('#e-'+(index+ 1)).css('width', $('#e-'+(index+ 1)).width() / 2).css('left', $('#e-'+(index + 1)).width());
}
})
}
})
// collision function to return boolean
// attribute: http://stackoverflow.com/questions/14012766/detecting-whether-two-divs-overlap
function collision($div1, $div2) {
let x1 = $div1.offset().left;
let y1 = $div1.offset().top;
let h1 = $div1.outerHeight(true);
let w1 = $div1.outerWidth(true);
let b1 = y1 + h1;
let r1 = x1 + w1;
let x2 = $div2.offset().left;
let y2 = $div2.offset().top;
let h2 = $div2.outerHeight(true);
let w2 = $div2.outerWidth(true);
let b2 = y2 + h2;
let r2 = x2 + w2;
if (b1 < y2 || y1 > b2 || r1 < x2 || x1 > r2) return false;
return true;
}
// render events using renderDay(arr) in console
html, body {
margin: 0;
padding: 0;
font-family: sans-serif;
}
#container {
height: 100%;
width: 100%;
}
#header-title {
text-align: center;
}
#calendar {
width: 400px;
height: 620px;
margin-top: 70px;
}
#events {
position: absolute;
top: 80px;
left: 100px;
width: 800px;
height: 620px;
}
.event {
box-shadow: 0 0 20px black;
border-radius: 5px;
}
.hr-block {
border-top: 2px solid black;
height: 58px;
margin: 0;
padding: 0;
margin-left: 100px;
min-width: 360px;
opacity: .5;
}
.hr-header {
position: relative;
top: -33px;
left: -68px;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css/styles.css">
<link rel="stylesheet" href="css/responsive.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.3/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script charset="UTF-8" src="js/moment.js"></script>
<script charset="UTF-8" src="js/script2.js"></script>
<title>Thought Machine Code Challenge</title>
</head>
<body>
<div id="container">
<div class="header">
<h1 id="header-title"></h1>
</div>
<div id="calendar">
<div class="hr-block"><h2 class="hr-header">09:00</h2></div>
<div class="hr-block"><h2 class="hr-header">10:00</h2></div>
<div class="hr-block"><h2 class="hr-header">11:00</h2></div>
<div class="hr-block"><h2 class="hr-header">12:00</h2></div>
<div class="hr-block"><h2 class="hr-header">13:00</h2></div>
<div class="hr-block"><h2 class="hr-header">14:00</h2></div>
<div class="hr-block"><h2 class="hr-header">15:00</h2></div>
<div class="hr-block"><h2 class="hr-header">16:00</h2></div>
<div class="hr-block"><h2 class="hr-header">17:00</h2></div>
<div class="hr-block"><h2 class="hr-header">18:00</h2></div>
</div>
</div>
<div id="events">
</div>
<script>
document.getElementById("header-title").innerHTML = moment().calendar();
</script>
</body>
</html>
events not rendering, you need to run:
renderDay([{start:30, end:120, start: 60, end: 120}]) in console
I'm have trouble with cloning an object using interact.js. I can do drag and drop, but there is no way to get clone from the objects.
I put here drag and drop code. Can someone modify it to clone objects?
#drag-1, #drag-2 {
width: 20%;
height: 10%;
min-height: 6.5em;
margin: 10%;
background-color: #29e;
color: white;
border-radius: 0.75em;
padding: 4%;
-webkit-transform: translate(0px, 0px);
transform: translate(0px, 0px);
}
#drag-me::before {
content: "#" attr(id);
font-weight: bold;
}
top:35px; left:40px; width:50px; height:50px;
z-index:99; background-color:#44ebfa;
}
<html>
<head>
<title>test 1 </title>
<!--<script type="text/javascript" src="d3-js/d3.min.js"></script>-->
<script type="text/javascript" src="www.googledrive.com/host/0B4A7r4wXVSe-SDdVdlNtbnhFZ2s"></script>
<link rel="stylesheet" type="text/css" href="test1_css1.css">
</head>
<body>
<script>
// target elements with the "draggable" class
interact('.draggable')
.draggable({
// enable inertial throwing
inertia: true,
// keep the element within the area of it's parent
restrict: {
restriction: "parent",
endOnly: true,
elementRect: { top: 0, left: 0, bottom: 1, right: 1 }
},
// call this function on every dragmove event
onmove: dragMoveListener,
// call this function on every dragend event
onend: function (event) {
var textEl = event.target.querySelector('p');
textEl && (textEl.textContent =
'moved a distance of '
+ (Math.sqrt(event.dx * event.dx +
event.dy * event.dy)|0) + 'px');
}
});
function dragMoveListener (event) {
var target = event.target,
// keep the dragged position in the data-x/data-y attributes
x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx,
y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy;
// translate the element
target.style.webkitTransform =
target.style.transform =
'translate(' + x + 'px, ' + y + 'px)';
// update the posiion attributes
target.setAttribute('data-x', x);
target.setAttribute('data-y', y);
}
// this is used later in the resizing demo
window.dragMoveListener = dragMoveListener;
</script>
<div id="drag-1" class="draggable">
<p> You can drag one element </p>
</div>
<div id="drag-2" class="draggable">
<p> with each pointer </p>
</div>
</body>
</html>
Try this code sample
interact('.draggable').draggable({
inertia: true,
restrict: {
restriction: "#visualizer-panel",
endOnly: true,
elementRect: { top: 0, left: 0, bottom: 1, right: 1 }
},
onmove: function (event) {
var target = event.target;
var x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx;
var y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy;
target.style.webkitTransform =
target.style.transform = 'translate(' + x + 'px, ' + y + 'px)';
target.setAttribute('data-x', x);
target.setAttribute('data-y', y);
},
onend: function(event) {
console.log(event);
}
}).on('move', function (event) {
var interaction = event.interaction;
if (interaction.pointerIsDown && !interaction.interacting() && event.currentTarget.getAttribute('clonable') != 'false') {
var original = event.currentTarget;
var clone = event.currentTarget.cloneNode(true);
var x = clone.offsetLeft;
var y = clone.offsetTop;
clone.setAttribute('clonable','false');
clone.style.position = "absolute";
clone.style.left = original.offsetLeft+"px";
clone.style.top = original.offsetTop+"px";
original.parentElement.appendChild(clone);
interaction.start({ name: 'drag' },event.interactable,clone);
}
});