I'm trying to build a draggable column based layout in JavaScript and having a bit of hard time with it.
The layout comprises of 3 columns (divs), with two dragable divs splitting each. The idea is that they are positioned absolutely and as you drag the draggers, the columns' respective widths, and left values are updated.
The three columns should always span the full width of the browser (the right most column is 100% width), but the other two should remain static by default when the browser is resized (which is why i'm using px, not %).
My code isn't working as of yet, I'm relatively new to JavaScript (which is why I don't want to use jQuery).
Having said that, there must be a more efficient (and cleaner) way of achieving this with less code that works (without reaching for the $ key).
If anyone with some awesome JS skills can help me out on this I'd be super-appreciative.
Here's the fiddle I'm working on http://jsfiddle.net/ZFwz5/3/
And here's the code:
HTML
<!-- colums -->
<div class="col colA"></div>
<div class="col colB"></div>
<div class="col colC"></div>
<!-- draggers -->
<div class="drag dragA" style="position: absolute; width: 0px; height: 100%; cursor: col-resize; left:100px;"><div></div></div>
<div class="drag dragB" style="position: absolute; width: 0px; height: 100%; cursor: col-resize; left: 300px;"><div></div></div>
CSS:
body {
overflow:hidden;
}
.col {
position: absolute;
height:100%;
left: 0;
top: 0;
overflow: hidden;
}
.colA {background:red;width:100px;}
.colB {background:green; width:200px; left:100px;}
.colC {background:blue; width:100%; left:300px;}
.drag > div {
background: 0 0;
position: absolute;
width: 10px;
height: 100%;
cursor: col-resize;
left: -5px;
}
and my terrible JavaScript:
//variabe columns
var colA = document.querySelector('.colA');
var colB = document.querySelector('.colB');
var colC = document.querySelector('.colC');
//variable draggers
var draggers = document.querySelectorAll('.drag');
var dragA = document.querySelector(".dragA");
var dragB = document.querySelector(".dragB");
var dragging = false;
function drag() {
var dragLoop;
var t = this;
var max;
var min;
if (dragging = true) {
if (this == dragA) {
min = 0;
max = dragB.style.left;
} else {
min = dragA.style.left;
max = window.innerWidth;
}
dragLoop = setInterval(function () {
var mouseX = event.clientX;
var mouseY = event.clientY;
if (mouseX >= max) {
mouseX = max;
}
if (mouseY <= min) {
mouseY = min;
}
t.style.left = mouseX;
updateLayout();
}, 200);
}
}
function updateLayout() {
var posA = dragA.style.left;
var posB = dragB.style.left;
colB.style.paddingRight = 0;
colA.style.width = posA;
colB.style.left = posA;
colB.style.width = posB - posA;
colC.style.left = posB;
colC.style.width = window.innerWidth - posB;
}
for (var i = 0; i < draggers.length; i++) {
draggers[i].addEventListener('mousedown', function () {
dragging = true;
});
draggers[i].addEventListener('mouseup', function () {
clearInterval(dragLoop);
dragging = false;
});
draggers[i].addEventListener('mouseMove', function () {
updateLayout();
drag();
});
}
I see a couple of things wrong here. First of all, the mousemove event only fires on an element when the mouse is over that element. You might have better luck registering a mousemove listener on the parent of your div.drag elements, then calculating the mouse's position inside that parent whenever a mouse event happens, then using that position to resize your columns and your draggers.
Second, I'm not quite sure what you're trying to do by registering a function with setInterval. You're doing pretty well with registering event listeners; why not continue to use them to change the state of your DOM? Why switch to a polling-based mechanism? (and the function you pass to setInterval won't work anyway - it refers to a variable named event, which in that context is undefined.)
This is just a little example... I hope it can help you :)
window.onload = function() {
var myDiv = document.getElementById('myDiv');
function show_coords(){
var monitor = document.getElementById('monitor');
var x = event.clientX - myDiv.clientWidth / 2;
var y = event.clientY - myDiv.clientWidth / 2;
monitor.innerText = "X: " + x + "\n" + "Y: " + y;
myDiv.style.left = x + "px";
myDiv.style.top = y + "px";
}
document.onmousemove = function(){
if(myDiv.innerText == "YES"){show_coords();}
}
myDiv.onmousedown = function(){
myDiv.innerText = "YES";
}
myDiv.onmouseup = function(){
myDiv.innerText = "NO";
}
}
Related
I have a large background image and some much smaller images for the user to drag around on the background. I need this to be efficient in terms of performance, so i'm trying to avoid libraries. I'm fine with drag 'n' drop if it work's well, but im trying to get drag.
Im pretty much trying to do this. But after 8 years there must be a cleaner way to do this right?
I currently have a drag 'n' drop system that almost works, but when i drop the smaller images, they are just a little off and it's very annoying. Is there a way to fix my code, or do i need to take a whole different approach?
This is my code so far:
var draggedPoint;
function dragStart(event) {
draggedPoint = event.target; // my global var
}
function drop(event) {
event.preventDefault();
let xDiff = draggedPoint.x - event.pageX;
let yDiff = draggedPoint.y - event.pageY;
let left = draggedPoint.style.marginLeft; // get margins
let top = draggedPoint.style.marginTop;
let leftNum = Number(left.substring(0, left.length - 2)); // cut off px from the end
let topNum = Number(top.substring(0, top.length - 2));
let newLeft = leftNum - xDiff + "px" // count new margins and put px back to the end
let newTop = topNum - yDiff + "px"
draggedPoint.style.marginLeft = newLeft;
draggedPoint.style.marginTop = newTop;
}
function allowDrop(event) {
event.preventDefault();
}
let imgs = [
"https://upload.wikimedia.org/wikipedia/commons/6/67/Orange_juice_1_edit1.jpg",
"https://upload.wikimedia.org/wikipedia/commons/f/ff/Solid_blue.svg",
"https://upload.wikimedia.org/wikipedia/commons/b/b4/Litoria_infrafrenata_-_Julatten.jpg"
]
/* my smaller images: */
for (let i = 0; i < 6; i++) {
let sensor = document.createElement("img");
sensor.src = imgs[i % imgs.length];
sensor.alt = i;
sensor.draggable = true;
sensor.classList.add("sensor");
sensor.style.marginLeft = `${Math.floor(Math.random() * 900)}px`
sensor.style.marginTop = `${Math.floor(Math.random() * 500)}px`
sensor.onclick = function() {
sensorClick(logs[i].id)
};
sensor.addEventListener("dragstart", dragStart, null);
let parent = document.getElementsByClassName("map")[0];
parent.appendChild(sensor);
}
<!-- my html: -->
<style>
.map {
width: 900px;
height: 500px;
align-content: center;
margin: 150px auto 150px auto;
}
.map .base {
position: absolute;
width: inherit;
height: inherit;
}
.map .sensor {
position: absolute;
width: 50px;
height: 50px;
}
</style>
<div class="map" onDrop="drop(event)" ondragover="allowDrop(event)">
<img src='https://upload.wikimedia.org/wikipedia/commons/f/f7/Plan-Oum-el-Awamid.jpg' alt="pohja" class="base" draggable="false">
<div>
With the answers from here and some time i was able to get a smooth drag and click with pure js.
Here is a JSFiddle to see it in action.
let maxLeft;
let maxTop;
const minLeft = 0;
const minTop = 0;
let timeDelta;
let imgs = [
"https://upload.wikimedia.org/wikipedia/commons/6/67/Orange_juice_1_edit1.jpg",
"https://upload.wikimedia.org/wikipedia/commons/f/ff/Solid_blue.svg",
"https://upload.wikimedia.org/wikipedia/commons/b/b4/Litoria_infrafrenata_-_Julatten.jpg"
]
var originalX;
var originalY;
window.onload = function() {
document.onmousedown = startDrag;
document.onmouseup = stopDrag;
}
function sensorClick () {
if (Date.now() - timeDelta < 150) { // check that we didn't drag
createPopup(this);
}
}
// create a popup when we click
function createPopup(parent) {
let p = document.getElementById("popup");
if (p) {
p.parentNode.removeChild(p);
}
let popup = document.createElement("div");
popup.id = "popup";
popup.className = "popup";
popup.style.top = parent.y - 110 + "px";
popup.style.left = parent.x - 75 + "px";
let text = document.createElement("span");
text.textContent = parent.id;
popup.appendChild(text);
var map = document.getElementsByClassName("map")[0];
map.appendChild(popup);
}
// when our base is loaded
function baseOnLoad() {
var map = document.getElementsByClassName("map")[0];
let base = document.getElementsByClassName("base")[0];
maxLeft = base.width - 50;
maxTop = base.height - 50;
/* my smaller images: */
for (let i = 0; i < 6; i++) {
let sensor = document.createElement("img");
sensor.src = imgs[i % imgs.length];
sensor.alt = i;
sensor.id = i;
sensor.draggable = true;
sensor.classList.add("sensor");
sensor.classList.add("dragme");
sensor.style.left = `${Math.floor(Math.random() * 900)}px`
sensor.style.top = `${Math.floor(Math.random() * 500)}px`
sensor.onclick = sensorClick;
let parent = document.getElementsByClassName("map")[0];
parent.appendChild(sensor);
}
}
function startDrag(e) {
timeDelta = Date.now(); // get current millis
// determine event object
if (!e) var e = window.event;
// prevent default event
if(e.preventDefault) e.preventDefault();
// IE uses srcElement, others use target
targ = e.target ? e.target : e.srcElement;
originalX = targ.style.left;
originalY = targ.style.top;
// check that this is a draggable element
if (!targ.classList.contains('dragme')) return;
// calculate event X, Y coordinates
offsetX = e.clientX;
offsetY = e.clientY;
// calculate integer values for top and left properties
coordX = parseInt(targ.style.left);
coordY = parseInt(targ.style.top);
drag = true;
document.onmousemove = dragDiv; // move div element
return false; // prevent default event
}
function dragDiv(e) {
if (!drag) return;
if (!e) var e = window.event;
// move div element and check for borders
let newLeft = coordX + e.clientX - offsetX;
if (newLeft < maxLeft && newLeft > minLeft) targ.style.left = newLeft + 'px'
let newTop = coordY + e.clientY - offsetY;
if (newTop < maxTop && newTop > minTop) targ.style.top = newTop + 'px'
return false; // prevent default event
}
function stopDrag() {
if (typeof drag == "undefined") return;
if (drag) {
if (Date.now() - timeDelta > 150) { // we dragged
let p = document.getElementById("popup");
if (p) {
p.parentNode.removeChild(p);
}
} else {
targ.style.left = originalX;
targ.style.top = originalY;
}
}
drag = false;
}
.map {
width: 900px;
height: 500px;
margin: 50px
position: relative;
}
.map .base {
position: absolute;
width: inherit;
height: inherit;
}
.map .sensor {
display: inline-block;
position: absolute;
width: 50px;
height: 50px;
}
.dragme {
cursor: move;
left: 0px;
top: 0px;
}
.popup {
position: absolute;
display: inline-block;
width: 200px;
height: 100px;
background-color: #9FC990;
border-radius: 10%;
}
.popup::after {
content: "";
position: absolute;
top: 100%;
left: 50%;
margin-left: -10px;
border-width: 10px;
border-style: solid;
border-color: #9FC990 transparent transparent transparent;
}
.popup span {
width: 90%;
margin: 10px;
display: inline-block;
text-align: center;
}
<div class="map" width="950px" height="500px">
<img src='https://upload.wikimedia.org/wikipedia/commons/f/f7/Plan-Oum-el-Awamid.jpg' alt="pohja" class="base" draggable="false" onload="baseOnLoad()">
<div>
I'm trying to create a simple demo where rolling over a pseudo element will change the style of its parent. In other words, I want to be able to roll over the letter e in the top right corner of the image and then display the text content.
I've managed to get it working when rolling over the image itself, but not the pseudo element. I've commented out the working code for rolling over the image itself, and left the incorrect pseudo rollover code uncommented out.
I wonder whether you can actually select pseudo elements in JS as it shows null when trying to select any pseudo element.
Any ideas would be appreciated. Thanks for any help. The code is below:
Codepen: https://codepen.io/anon/pen/NZvdzr
/*document.querySelector('#img-wrap').onmouseover = function() {
document.querySelector('#caption-wrap').style.opacity = 1;
}
document.querySelector('#img-wrap').onmouseout = function() {
document.querySelector('#caption-wrap').style.opacity = 0;
}*/
document.querySelector('#img-wrap:after').onmouseover = function() {
document.querySelector('#caption-wrap').style.opacity = 1;
}
document.querySelector('#img-wrap:after').onmouseout = function() {
document.querySelector('#caption-wrap').style.opacity = 0;
}
#img-wrap {
width: 30%;
position: relative;
}
#caption-wrap {
position: absolute;
top: 0;
right: 0;
opacity: 0;
}
img {
width: 100%;
}
#img-wrap:after {
content: 'e';
position: absolute;
top: 0;
right: 0;
}
<div id='img-wrap'>
<img src='https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ-bCnPPMYp6QIfrCr2BR-imm_Sw9IHCXIXzE5fei7R8PTBKYGd'>
<div id='caption-wrap'>
<p>some text will appear</p>
</div>
</div>
You cannot listen to pseudo elements, but you can find out some interesting info via window.getComputedStyle(). Below is a demo.
I'm listening to mouse move on the image element and comparing the coords to see if they fall between the rectangle of the pseudo element.
There is a padding of 2px on each tolerance, you could change that to something else if you want to be more forgiving with the mouse over detection.
CanIUse.com says window.getComputedStyle() is supported by all browsers, but I haven't tested if they all return the proper coordinate information for this to work – you should cross browser test this before using.
var element = document.querySelector('#img-wrap')
element.onmousemove = function(event){
var elementRect = element.getBoundingClientRect()
var pseudo = window.getComputedStyle(element, ':after')
var pseudoRect = {
top: parseFloat(pseudo.top),
left: parseFloat(pseudo.left),
width: parseFloat(pseudo.width),
height: parseFloat(pseudo.height),
}
var mouseX = event.clientX
var mouseY = event.clientY
var yTolTop = elementRect.top + pseudoRect.top - 2
var yTolBot = elementRect.top + pseudoRect.top + pseudoRect.height + 2
var xTolLeft = elementRect.left + pseudoRect.left - 2
var xTolRight = elementRect.left + pseudoRect.left + pseudoRect.width + 2
//console.log(elementRect.top, yTolTop, mouseY, yTolBot, " | ", elementRect.left, xTolLeft, mouseX, xTolRight)
if(mouseY > yTolTop && mouseY < yTolBot && mouseX > xTolLeft && mouseX < xTolRight){
document.querySelector('#caption-wrap').style.opacity = 1;
}else{
document.querySelector('#caption-wrap').style.opacity = 0;
}
}
element.onmouseout = function(){
document.querySelector('#caption-wrap').style.opacity = 0;
}
#img-wrap {
width: 30%;
position: relative;
}
#caption-wrap {
position: absolute;
top: 0;
right: 0;
opacity: 0;
}
img {
width: 100%;
}
#img-wrap:after {
content: 'e';
position: absolute;
top: 0;
right: 0;
}
<div id='img-wrap'>
<img src='https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ-bCnPPMYp6QIfrCr2BR-imm_Sw9IHCXIXzE5fei7R8PTBKYGd'>
<div id='caption-wrap'>
<p>some text will appear</p>
</div>
</div>
Codepen: https://codepen.io/bergy/pen/YoxZBp
(edit: since the JS was getting the rects outside of the mouse move function, if the element was ever moved it would stop working. Now it looks for rects in mouse move so the bug is fixed)
here is my jsfiddle http://jsfiddle.net/9XdV7/, how to make when hover in - text scroll and infinite loop, out - back to start position? now it can't infinite loop scroll
for (var i = 0; i < $('.list').length; i++) {
var this_el = $('.list').eq(i);
var interval = null;
$(this_el).hover(function() {
var that = $(this);
var this_indent = 0;
interval = setInterval(function(){
this_indent--;
if (this_indent == -($(that).find('.text').width())) {
clearInterval(interval);
this_indent = 0;
// how to loop scroll
}
$(that).css('text-indent', this_indent);
},20);
}, function() {
clearInterval(interval);
$(this).css('text-indent', 0);
});
}
html & css
<div class="list"><div class="text">stringstringstringstring</div></div>
<div class="list"><div class="text">stringstringstring</div></div>
<div class="list"><div class="text">stringstringstringstring</div></div>
.list {
width: 100px;
overflow: hidden;
white-space:nowrap;
height: 15px;
background-color: red;
margin: 10px;
}
.text {
text-align: left;
background-color: purple;
display: inline;
}
Is this what you're looking for? Fiddle
I used mouseenter and mouseleave instead of hover.
$(elem).on("mouseenter",function() { ... });
I stored the identifier belonging to the element in its data: $(this).data("interval",interval);
I added
if(this_indent < -150) {
this_indent = 100;
}
to make the effect infinite. -150 is a value I got from the developer tools. 100 is pure testing.
This still wants some tweaking for determining the point at which to loop, but I think it's basically what you want:
$(function() {
for (var i = 0; i < $('.list').length; i++) {
var this_el = $('.list').eq(i);
var interval = null;
$(this_el).hover(function() {
var that = $(this);
var this_indent = 0;
interval = setInterval(function(){
this_indent--;
if (this_indent < that.width() * -1)
this_indent = that.width();
that.css('text-indent', this_indent);
},20);
}, function() {
clearInterval(interval);
$(this).css('text-indent', 0);
});
}
});
It's looping based on the width of the div, rather than the actual length of the text. If you want the text width, you could look here: Calculating text width
I am working on a bit of code to have a dot circle around your cursor on a webpage, but I am having trouble getting the 'div' to follow the path I want; in fact, the object is not moving at all and I cannot figure out why my code does not work. Here is the code that is causing me trouble from what I've narrowed down:
<!DOCTYPE HTML>
<head>
<title>TEST SPACE</title>
<script src="jquery-1.10.2.min.js"></script>
</head>
<body>
<div id="test"></div>
<style>
#test {
background-color: black;
height: 10px;
width: 10px;
border-radius: 100%;
position: absolute;
top: 50%;
left: 50%;
}
</style>
<script>
const omega = Math.PI / 2000;
function dotRotation() {
var time = 0;
var x = 20*(Math.sin(omega*time));
var y = 20*(Math.cos(omega*time));
document.getElementById("test").style.marginLeft = x;
document.getElementById("test").style.marginTop = y;
time += 25;
};
$(document).ready(function() {
setInterval('dotRotation()',25);
});
</script>
</body>
JSFiddle
Two things are wrong
You need to move your time variable outside of the function
You need to give a unit to the value you pass to the margins so add +'px' after the variables .marginTop = y + 'px';
So altogether
const omega = Math.PI / 2000;
var time = 0;
function dotRotation() {
var x = 20*(Math.sin(omega*time));
var y = 20*(Math.cos(omega*time));
var style = document.getElementById("test").style;
style.marginLeft = x +'px';
style.marginTop = y +'px';
time += 25;
};
Demo at http://jsfiddle.net/mWC63/
Also you can cache references to the dom to avoid the searching
Your fiddle is set to run onload, it should be set up to run in the head. [drop down on left under jQuery selection.]
time is declared inside of the function so it is reset back to zero every single time it is called. Move it outside of dotRotation
var time = 0;
function dotRotation() {
var x = 20*(Math.sin(omega*time));
var y = 20*(Math.cos(omega*time));
$("#test").css({"marginLeft" : x + "px", "marginTop" : y + "px"});
time += 25;
};
$(function() {
setInterval(dotRotation,25);
});
I want to use the 'mouse's drag' to drag a background's position around, inside a box.
The CSS:
.filmmakers #map {
width : 920px;
height : 500px;
margin-top : 50px;
margin-left : 38px;
border : 1px solid rgb(0, 0, 0);
cursor : move;
overflow : hidden;
background-image : url('WorldMap.png');
background-repeat : no-repeat;
}
The html:
<div id = "map" src = "WorldMap.png" onmousedown = "MouseMove(this)"> </div>
The Javascript:
function MouseMove (e) {
var x = e.clientX;
var y = e.clientY;
e.style.backgroundPositionX = x + 'px';
e.style.backgroundPositionY = y + 'px';
e.style.cursor = "move";
}
Nothing happens, no errors, no warnings, nothing... I have tried lots of things: an absolutely positioned image inside a div (you can guess why that didn't work), A draggable div inside a div with a background image, a table with drag and drop, and finally I tried this:
function MouseMove () {
e.style.backgroundPositionX = 10 + 'px';
e.style.backgroundPositionY = 10 + 'px';
e.style.cursor = "move";
}
This works, but its not relative to the mouse's position, pageX and pageY don't work either.
A live demo: http://jsfiddle.net/VRvUB/224/
P.S: whatever your idea is, please don't write it in JQuery
From your question I understood you needed help implementing the actual "dragging" behavior. I guess not. Anyway, here's the results of my efforts: http://jsfiddle.net/joplomacedo/VRvUB/236/
The drag only happens when the mouse button, and.. well, it behaves as I think you might want it to. Just see the fiddle if you haven't =)
Here's the code for those who want to see it here:
var AttachDragTo = (function () {
var _AttachDragTo = function (el) {
this.el = el;
this.mouse_is_down = false;
this.init();
};
_AttachDragTo.prototype = {
onMousemove: function (e) {
if ( !this.mouse_is_down ) return;
var tg = e.target,
x = e.clientX,
y = e.clientY;
tg.style.backgroundPositionX = x - this.origin_x + this.origin_bg_pos_x + 'px';
tg.style.backgroundPositionY = y - this.origin_y + this.origin_bg_pos_y + 'px';
},
onMousedown: function(e) {
this.mouse_is_down = true;
this.origin_x = e.clientX;
this.origin_y = e.clientY;
},
onMouseup: function(e) {
var tg = e.target,
styles = getComputedStyle(tg);
this.mouse_is_down = false;
this.origin_bg_pos_x = parseInt(styles.getPropertyValue('background-position-x'), 10);
this.origin_bg_pos_y = parseInt(styles.getPropertyValue('background-position-y'), 10);
},
init: function () {
var styles = getComputedStyle(this.el);
this.origin_bg_pos_x = parseInt(styles.getPropertyValue('background-position-x'), 10);
this.origin_bg_pos_y = parseInt(styles.getPropertyValue('background-position-y'), 10);
//attach events
this.el.addEventListener('mousedown', this.onMousedown.bind(this), false);
this.el.addEventListener('mouseup', this.onMouseup.bind(this), false);
this.el.addEventListener('mousemove', this.onMousemove.bind(this), false);
}
};
return function ( el ) {
new _AttachDragTo(el);
};
})();
/*** IMPLEMENTATION ***/
//1. Get your element.
var map = document.getElementById('map');
//2. Attach the drag.
AttachDragTo(map);
This isn't working because you are passing the element "map" to your MouseMove function, and using it as both an event object and an element. You can fix this painlessly by using JavaScript to assign your event handler rather than HTML attributes:
<div id="map"></div>
And in your JavaScript:
document.getElementById('map').onmousemove = function (e) {
// the first parameter (e) is automatically assigned an event object
var x = e.clientX;
var y = e.clientY;
// The context of this is the "map" element
this.style.backgroundPositionX = x + 'px';
this.style.backgroundPositionY = y + 'px';
}
http://jsfiddle.net/VRvUB/229/
The downside of this approach is that the backgroundPositionX and backgroundPositionY style properties are not supported in all browsers.
You mention "an absolutely positioned image inside a div" which is probably the more compatible solution for this. To make this setup work, you need to set the position of the outer element to relative, which makes absolute child elements use its bounds as zero.
<div id="map">
<img src="" alt="">
</div>
CSS:
#map {
position:relative;
overflow:hidden;
}
#map img {
position:absolute;
left:0;
top:0;
}
Here it is applied to your code: http://jsfiddle.net/VRvUB/232/
This works 100%
Vanilla Javascript
document.getElementById('image').onmousemove = function (e) {
var x = e.clientX;
var y = e.clientY;
this.style.backgroundPositionX = -x + 'px';
this.style.backgroundPositionY = -y + 'px';
this.style.backgroundImage = 'url(https://i.ibb.co/vhL5kH2/image-14.png)';
}
document.getElementById('image').onmouseleave = function (e) {
this.style.backgroundPositionX = 0 + 'px';
this.style.backgroundPositionY = 0 + 'px';
this.style.backgroundImage = 'url(https://i.ibb.co/Ph9MCB2/template.png)';
}
.container {
max-width: 670px;
height: 377px;
}
#image {
max-width: 670px;
height: 377px;
cursor: crosshair;
overflow: hidden;
background-image: url('https://i.ibb.co/Ph9MCB2/template.png');
background-repeat: no-repeat;
}
<div class="container">
<div id="image">
</div>
</div>