How to create a horizontal, circular/scrollable menu? - javascript

How can we make a horizontal row of link elements (with variable width/text length) with overflow hidden (or without, depending on how this is usually done..) function so that the last element is positioned behind the first and so on in each left or right direction, to create a circular scroll?
I have this so far:
const horizontalContainer = document.querySelector('.horizontal-container')
const horizontalLinks = document.querySelectorAll('.horizontal-link')
let touchStart = 0
let touchX = 0
let isDragging = false
const handleTouchStart = (e) => {
touchStart = e.clientX || e.touches[0].clientX
isDragging = true
}
const handleTouchMove = (e) => {
if (!isDragging) return
touchX = e.clientX || e.touches[0].clientX
touchStart = touchX
horizontalLinks.forEach(element => {
element.style.transform = "translate(" + (touchStart) + "px," + "0px)";
})
}
const handleTouchEnd = () => {
isDragging = false
}
horizontalContainer.addEventListener('mousedown', handleTouchStart)
horizontalContainer.addEventListener('mousemove', handleTouchMove)
horizontalContainer.addEventListener('mouseleave', handleTouchEnd)
horizontalContainer.addEventListener('mouseup', handleTouchEnd)
horizontalContainer.addEventListener('selectstart', () => { return false })
.horizontal-container {
display: flex;
overflow-x: hidden;
width: 100%;
}
.horizontal-container::-webkit-scrollbar {
display: none;
}
.horizontal-link {
padding-left: 20px;
padding-right: 20px;
right: 0;
}
<div class="horizontal-container">
<div class="horizontal-link">
ONE
</div>
<div class="horizontal-link">
TWO
</div>
<div class="horizontal-link">
THREE
</div>
<div class="horizontal-link">
FOUR
</div>
<div class="horizontal-link">
FIVE
</div>
</div>
Edit: Unless you have the time to show me an example, I'm more than happy with just an explanation for how this can be done calculating translate: transform(x,y) to reposition the links when the left or right position of a link div of variable width reaches the right or left position of the screen depending on the screen width, which can also be variable, so that what the exact amount of overflow that peeks outside the viewport on the right will peek out the same amount on the left side of the viewport.
Edit2: Even though I know little about programming or the Javascript language (yet) I do know that this is not a "carousel" which is much easier to implement, that I already have created on my own so I know every detail of it. And a scrollbar is also programmed to move between a left end or right end position - this cannot be used here without a lot of ugly hacks so a new scrolling function needs to be implemented from scratch. I also know that jQuery will not help me to understand or learn more, and that this is nothing one would use - ever - whether you are an amateur or not.

What you are requesting is a carousel pattern. You can configure a carousel to show multiple slides at once. In this case each "slide" would be a menu item.
I have mocked up an example using https://kenwheeler.github.io/slick/
The only downfall is that it snaps to each slide, which may or may not be what you want. If it is not what you want then you would be best looking at other slider alternatives.
But the main point is that what you are requesting is normally done with a slider/carousel pattern. You just need to look at it differently, and you are not limited to show one "slide" at a time.
<link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/npm/slick-carousel#1.8.1/slick/slick.css"/>
<div class="menu-slider" style="width: 400px;">
<div style="padding:20px;">Link 1</div>
<div style="padding:20px;">Another Link 2</div>
<div style="padding:20px;">Yet Another Link 3</div>
<div style="padding:20px;">Menu Link 4</div>
<div style="padding:20px;">Link 5</div>
<div style="padding:20px;">Menu Link 6</div>
</div>
<script type="text/javascript" src="https://code.jquery.com/jquery-1.11.0.min.js"></script>
<script type="text/javascript" src="https://code.jquery.com/jquery-migrate-1.2.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/slick-carousel#1.8.1/slick/slick.min.js"></script>
<script type="text/javascript">
$(document).ready(function(){
$('.menu-slider').slick({
dots: false,
infinite: true,
centerMode: false,
variableWidth: true
});
});
</script>

Here's a working solution for one that will "fill" its parent in order to create the effect.
I was working on an alternative that didn't fill but it still needs more work.
$(function () {
init();
});
let base_width = 0;
function init() {
setupMenu();
}
function handleScroll(event) {
if (base_width === 0) {
// no need to do anything
return;
}
const $menu = $(event.currentTarget);
const scroll_left = $menu[0].scrollLeft;
// check backwards scroll
if (scroll_left <= base_width) {
const new_left = 2 * base_width - (base_width - scroll_left);
$menu[0].scrollLeft = new_left;
return;
}
if (scroll_left < base_width * 2) {
return;
}
// get remainder
const new_left = scroll_left % (base_width * 2);
$menu[0].scrollLeft = new_left + base_width;
}
function setupMenu() {
const $menu = $("#menu-fill");
const $parent = $menu.parent();
const menu_width = $menu.width();
const parent_width = $parent.width();
if (menu_width >= parent_width) {
// no need to duplicate
return;
}
base_width = menu_width;
// setup a base to clone
const $template = $menu.clone();
// get num times to duplicate to "fill" menu (i.e. allow scrolling)
// NOTE: we duplicate 1 "extra" so that we can scroll "backwards"
const num_duplicate = Math.ceil(parent_width / menu_width) + 2;
for (let i = 0; i < num_duplicate; i++) {
const $new_menu = $template.clone();
$menu.append($new_menu.children());
$new_menu.remove();
}
$menu[0].scrollLeft = base_width;
$menu.scroll(handleScroll)
}
* {
box-sizing: border-box;
font-family: sans-serif;
}
*::-webkit-scrollbar {
background-color: transparent;
height: 6px;
width: 6px;
border-radius: 3px;
}
*::-webkit-scrollbar-thumb {
background-color: #ccc;
border-radius: 3px;
}
html,
body {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}
.wrap {
width: 250px;
height: 100px;
border: 2px solid #777;
margin: 0 auto 20px auto;
}
.wrap.fill {
width: 500px;
}
.menu {
display: inline-flex;
max-width: 100%;
overflow: auto;
}
.item {
padding: 5px 10px;
background-color: #426ac0;
color: #fff;
border-right: 1px solid #1d3464;
white-space: nowrap;
}
.item:last-child {
border-right: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="wrap fill">
<div class="menu" id="menu-fill">
<div class="item">Item 1</div>
<div class="item">Number 2</div>
</div>
</div>

Related

How to use getElementsByClassName() for adding both grids and display one after another?

I Have Tried to add grid-2 when we press a key, but it's not working as I expected.
When it is displaying grid-2 I want the grid to disappear (I don't know how to do).
(background):-
I have searched and found there is a visibility property in CSS, but don't know how to apply to the whole grid and undo the visibility property and make them visible.
I have tried to add the grid-2 by getElementById but both the grids are appearing at a time.
(don't know how to make them appear one after another).
let curr_div_on = 0,curr_div_off = 0;
const key = document.getElementsByClassName('keys');
function setPlayingOn() {
key[curr_div_on % 4].classList.add("playing");
curr_div_on = (curr_div_on + 1) % 4;
}
function setPlayingOff() {
key[curr_div_off % 4].classList.remove("playing");
curr_div_off = (curr_div_off + 1) % 4;
}
setInterval(setPlayingOn, 500);
setTimeout(() => setInterval(setPlayingOff, 500), 500);
document.addEventListener('keypress', function(){
if(curr_div_on ==1){
var element = document.getElementsByClassName("grid-2");
element.classList.add("grid");
}
})
.grid{
display: grid;
grid-template-columns: auto auto;
grid-gap:10px;
}
.key{
padding: 20px;
border: 1px solid;
background-color: #2196F3;
text-align:center;
}
.playing{
transform: scale(1,1);
border-color: #ffc600;
box-shadow: 0 0 1rem #ffc600;
}
<html>
<div class='grid'>
<div class='key'>ABCD</div>
<div class='key'>EFGH</div>
<div class='key'>IJKL</div>
<div class='key'>LMNO</div>
</div>
<div class='grid-2'>
<div class='button'>A</div>
<div class='button'>B</div>
<div class='button'>C</div>
<div class='button'>D</div>
</div>
</html>
I think the code is wrong in these two points:
const key = document.getElementsByClassName('keys'); --> The selector should be 'key'
var element = document.getElementsByClassName("grid-2"); --> The result is a list (DomTokenList), so you should take the first element (var element = document.getElementsByClassName("grid-2")[0];)

Use elementFromPoint combined with scroll position

I would ask for help for the following problem. Given is a long list of divs with names. Each div with a name has a data-index with the initial letter of that person.
A small div sticked to the scrollbar indicator should name the initial letter from the current person next to it (Indicated by the blue line). Further the Text inside the div should be styled red otherwise black and the header of 100px should be respected
This is what I have:
window.onscroll = function(event){
// Variables for scroll pos calculation
var viewPortHeight = window.innerHeight;
var documentHeight = document.documentElement.scrollHeight;
var scrolledAmount = document.documentElement.scrollTop;
var indicatorTop = scrolledAmount * viewPortHeight / documentHeight;
// Highlight current active name and get data attr
// THIS PART IS NOT WORKING
var el = document.elementFromPoint(0, scrolledAmount);
el.style.color = 'red';
var el_text = el.dataset.index;
// Stick the index div to the scrollbar
if (indicatorTop > 110) {
document.getElementById('index').style.marginTop = indicatorTop-110+'px';
document.getElementById('line').style.marginTop = indicatorTop-110+'px';
document.getElementById("index").innerHTML = el_text;
} else {
document.getElementById('index').style.marginTop = '0px';
document.getElementById('line').style.marginTop = '0px';
}
}
body, html {
margin: 0;
}
.person__wrapper {
margin-top: 120px;
}
.person__item {
padding: 20px;
font-size: 20px;
background: #efefef;
margin: 20px;
width: 90%
}
#index, #line{
position: fixed;
top: 110px;
right: 0;
padding: 10px;
background: #0000ff;
color: #fff;
font-family: ClarendonBT-Heavy
}
#line {
width: 100%;
height: 1px;
padding: 0;
margin:0
}
.header__wrapper {
position: fixed;
top: 0;
width: 100%;
height: 100px;
background: white;
border-bottom: 2px solid #000;
opacity: 0.3;
}
<header class="header__wrapper"></header>
<section class="person__wrapper">
<article data-index="A" class="person__item">Adam</article>
<article data-index="B" class="person__item">Bobby</article>
<article data-index="C" class="person__item">Carla</article>
<article data-index="D" class="person__item">Debby</article>
<article data-index="E" class="person__item">Emil</article>
<article data-index="F" class="person__item">F</article>
<article data-index="G" class="person__item">G</article>
<article data-index="H" class="person__item">H</article>
<article data-index="I" class="person__item">I</article>
<article data-index="J" class="person__item">J</article>
<article data-index="K" class="person__item">K</article>
<article data-index="L" class="person__item">L</article>
<article data-index="M" class="person__item">M...</article>
<article data-index="N" class="person__item">N...</article>
<article data-index="O" class="person__item">O...</article>
<article data-index="P" class="person__item">P...</article>
<article data-index="Q" class="person__item">Q...</article>
<article data-index="R" class="person__item">R...</article>
<article data-index="S" class="person__item">S...</article>
<article data-index="T" class="person__item">T...</article>
<article data-index="U" class="person__item">U...</article>
<article data-index="V" class="person__item">V...</article>
<article data-index="W" class="person__item">W...</article>
<article data-index="X" class="person__item">X...</article>
<article data-index="Y" class="person__item">Y...</article>
<article data-index="Z" class="person__item">Z...</article>
</section>
<div id="index">A</div>
<div id="line"></div>
Here is a working fiddle: https://codepen.io/t-book/pen/zYvZzxV
All is working fine except elementFromPoint is not targeting the element which has the y amount of my scrolled height but all elements (all get red). Further, the data attribute is not updated inside by index div.
Can one push me in the right direction?
I found your question while searching a problem of my own. Little stale, but if you're still looking for a solution maybe this will help. There were a few issues going on:
elementFromPoint uses coordinates relative to the viewport, not the document. So using a y value of scrolledAmount probably wasn't doing what you really wanted. 0 would be the top of the viewport. More on this in a bit.
but...since the header is fixed, it always gets returned. Two ways to solve this. The easiest is to add css pointer-events:none on the header. Then it gets ignored. But if you need pointer events, the alternative would be to use elementsFromPoint which will return an array, and you can skip the header and grab the next element in the array.
since you've got a margin on your items, when the margin passes over the point you're querying it "falls through" to the underlying section tag. Unfortunately the best way around this seems to be a short for loop that can move the point slightly until you find the next article tag, and break the loop as soon as a match is found. (I wish there was a better way...that's what I was looking for when I found your question!)
I'm also using an x coord of window.innerWidth/2 for the same reason, to get past left margins and such. Of course this could be tweaked to meet your needs.
the reason why all of the headers were turning red is because fairly quickly after you started scrolling, your elementFromPoint was falling through and hitting a higher level element. And since color is an inherited property, it was being applied on all the articles as children. This can be prevented by the .closest('article') thing I added. That way if it's not an article or a descendant of an article, it will be null...not the wrong element.
window.onscroll = function(event) {
// Variables for scroll pos calculation
var viewPortHeight = window.innerHeight;
var documentHeight = document.documentElement.scrollHeight;
var scrolledAmount = document.documentElement.scrollTop;
var indicatorTop = scrolledAmount * viewPortHeight / documentHeight;
// Highlight current active name and get data attr
for (var i = 0; i < 80; i += 10) {
var el = document.elementFromPoint(window.innerWidth / 2, i).closest('article');
if (el) break;
}
if (el) {
el.style.color = 'red';
var el_text = el.dataset.index;
}
document.getElementById("index").innerHTML = el_text || 'A';
// Stick the index div to the scrollbar
if (indicatorTop > 110) {
document.getElementById('index').style.marginTop = indicatorTop - 110 + 'px';
document.getElementById('line').style.marginTop = indicatorTop - 110 + 'px';
} else {
document.getElementById('index').style.marginTop = '0px';
document.getElementById('line').style.marginTop = '0px';
}
}
body,
html {
margin: 0;
}
.person__wrapper {
margin-top: 120px;
}
.person__item {
padding: 20px;
font-size: 20px;
background: #efefef;
margin: 20px;
width: 90%;
box-sizing: content-box;
}
#index,
#line {
position: fixed;
top: 110px;
right: 0;
padding: 10px;
background: #0000ff;
color: #fff;
font-family: ClarendonBT-Heavy
}
#line {
width: 100%;
height: 1px;
padding: 0;
margin: 0
}
.header__wrapper {
position: fixed;
top: 0;
width: 100%;
height: 100px;
background: white;
border-bottom: 2px solid #000;
opacity: 0.3;
}
<header class="header__wrapper" style="pointer-events:none"></header>
<section class="person__wrapper">
<article data-index="A" class="person__item">Adam</article>
<article data-index="B" class="person__item">Bobby</article>
<article data-index="C" class="person__item">Carla</article>
<article data-index="D" class="person__item">Debby</article>
<article data-index="E" class="person__item">Emil</article>
<article data-index="F" class="person__item">F</article>
<article data-index="G" class="person__item">G</article>
<article data-index="H" class="person__item">H</article>
<article data-index="I" class="person__item">I</article>
<article data-index="J" class="person__item">J</article>
<article data-index="K" class="person__item">K</article>
<article data-index="L" class="person__item">L</article>
<article data-index="M" class="person__item">M...</article>
<article data-index="N" class="person__item">N...</article>
<article data-index="O" class="person__item">O...</article>
<article data-index="P" class="person__item">P...</article>
<article data-index="Q" class="person__item">Q...</article>
<article data-index="R" class="person__item">R...</article>
<article data-index="S" class="person__item">S...</article>
<article data-index="T" class="person__item">T...</article>
<article data-index="U" class="person__item">U...</article>
<article data-index="V" class="person__item">V...</article>
<article data-index="W" class="person__item">W...</article>
<article data-index="X" class="person__item">X...</article>
<article data-index="Y" class="person__item">Y...</article>
<article data-index="Z" class="person__item">Z...</article>
</section>
<div id="index">A</div>
<div id="line"></div>
*edit: I ended up finding another way to get an element near or under the cursor, in spite of the "margin" problem. But it's not any simpler and I'm not sure if it's really better. It uses caretRangeFromPoint.
var el;
var rng = document.caretRangeFromPoint(window.innerWidth / 2, 0);
//console.log(rng);
if (rng) {
el = rng.startContainer;
if (!(el instanceof Element)) el = el.parentElement;
el = el.closest('article');
}

Resizing multiple divs with mouse clicks

I took a look at this article and I wanted to try this same technique with more divs.
The above code works with 2 divs but not 4 divs. I tried to figure out why so I decided to try the following code.
var handler = document.querySelector('.handler');
var wrapperWidth;
var wrapper = handler.closest('.wrapper');
var box = wrapper.querySelector('.box');
var isHandlerDragging = false;
document.addEventListener('mousedown', function(e) {
// If mousedown event is fired from .handler, toggle flag to true
if (e.target === handler) {
isHandlerDragging = true;
}
});
document.addEventListener('mousemove', function(e) {
// Don't do anything if dragging flag is false
if (!isHandlerDragging) {
return false;
}
// Get offset
var containerOffsetLeft = wrapper.offsetLeft;
// Get x-coordinate of pointer relative to container
var pointerRelativeXpos = e.clientX - containerOffsetLeft;
// Arbitrary minimum width set on box A, otherwise its inner content will collapse to width of 0
var boxAminWidth = 60;
// Resize box A
// * 8px is the left/right spacing between .handler and its inner pseudo-element
// * Set flex-grow to 0 to prevent it from growing
wrapperWidth = wrapper.stlye.width;
box.style.width = (Math.max(boxAminWidth, wrapperWidth - 8)) + 'px';
box.style.flexGrow = 0;
});
document.addEventListener('mouseup', function(e) {
// Turn off dragging flag when user mouse is up
isHandlerDragging = false;
});
.wrapper {
background-color: #fff;
color: #444;
/* Use flexbox */
display: flex;
}
.box {
background-color: #444;
color: #fff;
border-radius: 5px;
padding: 20px;
font-size: 150%;
/* Use box-sizing so that element's outerwidth will match width property */
box-sizing: border-box;
/* Allow box to grow and shrink, and ensure they are all equally sized */
flex: 1 1 1 1 auto;
}
.handler {
width: 20px;
padding: 0;
cursor: ew-resize;
flex: 0 0 auto;
}
.handler::before {
content: '';
display: block;
width: 4px;
height: 100%;
background: red;
margin: 0 auto;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<div class="wrapper">
<div class="box">A</div>
<div class="handler"></div>
<div class="box">B</div>
<div class="handler"></div>
<div class="box">C</div>
<div class="handler"></div>
<div class="box">D</div>
</div>
</body>
</html>
What I wanted to have happen was the divs to be arrangeable.
You can take a look at the code here.
https://jsfiddle.net/paralaxwombat/1Lfqdb6x/
If this is what you want
var wrapper = document.querySelector('.wrapper');
var box = null;
var isHandlerDragging = false;
var boxAminWidth = 60;
var new_width = 0, current_width = 0;
document.addEventListener('mousedown', function(e) {
// If mousedown event is fired from .handler, toggle flag to true
if (e.target.classList.contains('handler')) {
isHandlerDragging = true;
box = e.target.previousElementSibling;
}
});
document.addEventListener('mousemove', function(e) {
// Don't do anything if dragging flag is false or
if (!isHandlerDragging) {
return false;
}
// save the current box width
current_width = box.style.width;
// check the minimum width
if ((new_width = e.clientX - box.offsetLeft - 8 ) >= boxAminWidth) {
box.style.width = new_width + 'px';
}
// make sure the boxs dont go past the wrapper, aka: the overflow effect
//if they do, we recover the last width of the current box to keep the boxs inside the wrapper.
if(wrapper.lastElementChild.offsetLeft + wrapper.lastElementChild.offsetWidth > wrapper.offsetWidth) {
box.style.width = current_width;
}
});
document.addEventListener('mouseup', function(e) {
// Turn off dragging flag when user mouse is up
isHandlerDragging = false;
});
.wrapper {
background-color: #fff;
color: #444;
/* Use flexbox */
display: flex;
}
.box {
background-color: #444;
color: #fff;
border-radius: 5px;
padding: 20px;
font-size: 150%;
/* Use box-sizing so that element's outerwidth will match width property */
box-sizing: border-box;
/* Allow box to grow and shrink, and ensure they are all equally sized */
flex: 1 1 1 1 auto;
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
flex-grow: 0;
flex-shrink: 0;
}
.handler {
width: 20px;
padding: 0;
cursor: ew-resize;
flex: 0 0 auto;
}
.handler::before {
content: '';
display: block;
width: 4px;
height: 100%;
background: red;
margin: 0 auto;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<div class="wrapper">
<div class="box">A</div>
<div class="handler"></div>
<div class="box">B</div>
<div class="handler"></div>
<div class="box">C</div>
<div class="handler"></div>
<div class="box">D</div>
</div>
</body>
</html>
I have some observations:
var handler = document.querySelector('.handler');
this line of code (unlike jQuery) selects only the first handler, not all of them, so this check if (e.target === handler) is valid only for the first handler, thus the mousemove won't work on all of them.
same thing goes for var box = wrapper.querySelector('.box');, you'll be always setting with to the first box.
This is the new javaScript code
var wrapper = document.querySelector('.wrapper');
var box = null;
var isHandlerDragging = false;
var boxAminWidth = 60;
var new_width = 0, current_width = 0;
document.addEventListener('mousedown', function(e) {
// If mousedown event is fired from .handler, toggle flag to true
if (e.target.classList.contains('handler')) {
isHandlerDragging = true;
box = e.target.previousElementSibling;
}
});
document.addEventListener('mousemove', function(e) {
// Don't do anything if dragging flag is false or
if (!isHandlerDragging) {
return false;
}
// save the current box width
current_width = box.style.width;
// check the minimum width
if ((new_width = e.clientX - box.offsetLeft - 8 ) >= boxAminWidth) {
box.style.width = new_width + 'px';
}
// make sure the boxs dont go past the wrapper, aka: the overflow effect
//if they do, we recover the last width of the current box to keep the boxs inside the wrapper.
if(wrapper.lastElementChild.offsetLeft + wrapper.lastElementChild.offsetWidth > wrapper.offsetWidth) {
box.style.width = current_width;
}
});
document.addEventListener('mouseup', function(e) {
// Turn off dragging flag when user mouse is up
isHandlerDragging = false;
});
In CSS, I made a small change in the box class:
.box {
/* ... */
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
flex-grow: 0;
flex-shrink: 0;
}

How can I enable draggable when mouse is already down on element and already moved?

I have code written to allow an HTML element to be dragged after the mouse has been down over that element for a certain period of time.
The problem is that when I am using native HTML drag and drop, and I enable the draggable property when this timeout is up (the mouse has been down on that element for that period of time), if the mouse had been moved while it was down before that timeout was up, HTML will not trigger a dragstart event or even start dragging the element.
There is an example below.
var t;
function startDelayedDrag() {
clearTimeout(t);
document.getElementById('dragtarget').draggable = false;
console.log('mousedown')
t = setTimeout(function() {
console.log('dragging enabled')
document.getElementById('dragtarget').draggable = true;
}, 1000);
}
.droptarget {
float: left;
width: 100px;
height: 35px;
margin: 15px;
padding: 10px;
border: 1px solid #aaaaaa;
user-select: none;
}
<div class="droptarget">
<p onmousedown="startDelayedDrag()" id="dragtarget">Drag me!</p>
</div>
<div class="droptarget"></div>
This one is tricky and it might be different from what you had in mind, but here is goes an idea how to solve your issue:
Start the drag event
Hide the drag object by setting an image using setDragImage
Clone the drag element node, hide the clone and add it to the document (since it's not possible to change the image set by setDragImage)
Start the timeout to change the visibility of the ghost element
This could be yet improved in many ways, but I think you can get the mechanics of how it works as it is. As a reference see the following snippet:
const [$drag] = document.getElementsByClassName('drag')
const [$pixel] = document.getElementsByClassName('pixel')
let $ghost = null
$drag.addEventListener("dragstart", e => {
// set the current draged element invisible
e.dataTransfer.setDragImage($pixel, 0, 0)
// create a ghost element
$ghost = $drag.cloneNode(true)
$ghost.style.position = "absolute"
$ghost.style.display = "none"
document.body.appendChild($ghost)
setTimeout(() => {
$ghost.style.display = 'block'
}, 1000)
})
$drag.addEventListener("drag", e => {
// keep the ghost position to follow the mouse while dragging
$ghost.style.left = `${e.clientX}px`
$ghost.style.top = `${e.clientY}px`
}, false);
$drag.addEventListener("dragend", e => {
// remove the ghost
if ($ghost.parentNode) $ghost.parentNode.removeChild($ghost)
}, false)
.content {
display: flex;
}
.box {
width: 100px;
height: 35px;
padding: 10px;
margin: 10px;
border: 1px solid #aaaaaa;
}
.drop {
user-select: none;
}
.drag {
text-align: center;
}
.pixel {
width: 1px;
height: 1px;
background-color: white;
}
<div class="content">
<div draggable="true" class="drag box">Drag</div>
<div class="drop box"></div>
<div class="pixel"></div>
</div>

How do you create 3 adjustable divs?

What I want:
| A | | B | | C |
^ ^
When you move the handles left and right A, B, and C resize accordingly
| A | | B | | C |
What I have is the || between B and C sliding, but not resizing B and all I get on the other one is the resize cursor. Basically C is a curtain and covers A and B. I did get min size working for C.
| A | C |
I broke somebody else's perfectly good code to get this far:
var isResizing = false,
who='',
lastDownX = 0;
$(function () {
var container = $('#container'),
left = $('#left'),
right = $('#right'),
middle = $('#middle'),
hand2 = $('#hand2'),
handle = $('#handle');
handle.on('mousedown', function (e) {
isResizing = true;
who=e.target.id;
lastDownX = e.clientX;
});
$(document).on('mousemove', function (e) {
var temp, min;
// we don't want to do anything if we aren't resizing.
if (!isResizing)
return;
min=container.width() * 0.1;
temp = container.width() - (e.clientX - container.offset().left);
if (temp < min)
temp = min;
if (who == 'handle')
right.css('width', temp);
if (who == 'hand2')
left.css('width', temp);
}).on('mouseup', function (e) {
// stop resizing
isResizing = false;
});
});
body, html {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
#container {
width: 100%;
height: 100%;
/* Disable selection so it doesn't get annoying when dragging. */
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: moz-none;
-ms-user-select: none;
user-select: none;
}
#container #left {
width: 40%;
height: 100%;
float: left;
background: red;
}
#container #middle {
margin-left: 40%;
height: 100%;
background: green;
}
#container #right {
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: 200px;
background: rgba(0, 0, 255, 0.90);
}
#container #handle {
position: absolute;
left: -4px;
top: 0;
bottom: 0;
width: 80px;
cursor: w-resize;
}
#container #hand2 {
position: absolute;
left: 39%;
top: 0;
bottom: 0;
width: 80px;
cursor: w-resize;
}
<div id="container">
<!-- Left side -->
<div id="left"> This is the left side's content!</div>
<!-- middle -->
<div id="middle">
<div id="hand2"></div> This is the middle content!
</div>
<!-- Right side -->
<div id="right">
<!-- Actual resize handle -->
<div id="handle"></div> This is the right side's content!
</div>
</div>
Been playing with it here: https://jsfiddle.net/ju9zb1he/5/
I was looking for a solution that required less extensive CSS. It does have one minor bug(FIXED), but hopefully this should get you started. Here is a DEMO.
Also I aimed to use DOM Traversal methods like .next() and .prev() that way it wouldn't be so attribute dependent, and would be easily reusable if you needed a feature like this multiple times on a page.
Edit - Further Explanation
The idea here is onClick of a .handle we want to gather the total width (var tWidth) of the .prev() and .next() divs relative to the .handle in the DOM. We can then use the start mouse position (var sPos) to substract the amount of pixels we've moved our mouse (e.pageX). Doing so gives us the correct width that the .prev() div should have on mousemove. To get the width of the .next() div we need only to subtract the width of the .prev() div from the total width (var tWidth) that we stored onClick of the .handle. Hope this helps! Let me know if you have any further questions, however I will likely be unavailable till tomorrow.
HTML
<div class="container">
<div id="left"></div>
<div id="l-handle" class="handle"></div>
<div id="middle"></div>
<div id="r-handle" class="handle"></div>
<div id="right"></div>
</div>
CSS
#left, #middle, #right {
display: inline-block;
background: #e5e5e5;
min-height: 200px;
margin: 0px;
}
#l-handle, #r-handle {
display: inline-block;
background: #000;
width: 2px;
min-height: 200px;
cursor: col-resize;
margin: 0px;
}
jQuery
var isDragging = false,
cWidth = $('.container').width(),
sPos,
handle,
tWidth;
$('#left, #middle, #right').width((cWidth / 3) - 7); // Set the initial width of content sections
$('.handle').on('mousedown', function(e){
isDragging = true;
sPos = e.pageX;
handle = $(this);
tWidth = handle.prev().width() + handle.next().width();
});
$(window).on('mouseup', function(e){
isDragging = false;
});
$('.container').on('mousemove', function(e){
if(isDragging){ // Added an additional condition here below
var cPos = sPos - e.pageX;
handle.prev().width((tWidth / 2) - cPos); // This was part of the bug...
handle.next().width(tWidth - handle.prev().width());
// Added an update to sPos here below
}
});
Edit
The bug was caused by 2 things.
1) On mousemove we were dividing the total width by two, instead of an updated mouse offset.
2) The sPos was not updating on mousemove, and stayed a static number based off of the click location.
Resolution
Update the sPos on mousemove that way the mouse offset is accurately based off of the previous mousemove position, rather than the click position. When this is done we can then subtract the .next() div's width from the total width. Then we subtract our current mouse position from the remaining width. The fiddle has been updated as well.
$('.container').on('mousemove', function(e){
var cPos = sPos - e.pageX;
if(isDragging && ((tWidth - handle.next().width()) - cPos) <= tWidth){
handle.prev().width((tWidth - handle.next().width()) - cPos);
handle.next().width(tWidth - handle.prev().width());
sPos = e.pageX;
}
});
Edit
Added an additional condition on mousemove to prevent the drag from exceeding the total width (var tWidth).
Can you please explain what you're trying to accomplish?
I don't believe you need to use position: absolute. The premise of absolute positioning is to override the margin and padding imposed on an element by its parent.
You don't need to do this, all elements have relative positioning by default which makes them push eachother around and don't allow overlapping.
I'm probably missing something, but I think this is what you want with nothing but some very basic CSS: http://jsfiddle.net/3bdoazpk/
<div class='first'>
asdf
</div><div class='second'>
dasdf
</div><div class='third'>
sadf
</div>
body {
margin: 0;
}
div {
display: inline-block;
height: 100%;
}
.first, .third {
width: 40%;
}
.first {
background-color: red;
}
.second {
background-color: blue;
width: 20%;
}
.third {
background-color: green;
}

Categories

Resources