I'm trying to replicate what is seen in this gif:
Grid Layout
I have to randomize the colors of each div inside the grid-container every second using setInterval() and querySelectorAll() after clicking the button on the top left corner, and display the current time as seen in the GIF. I know that the colors are randomly selected and actually use the CSS rgba() function. Here's what I have in my html file:
<!DOCTYPE html>
<html>
<head>
<style>
.item1 { grid-area: header; }
.item2 { grid-area: menu; }
.item3 { grid-area: main; }
.item4 { grid-area: right; }
.item5 { grid-area: footer; }
.grid-container {
display: grid;
grid-template-areas:
'header header header header header header'
'menu main main main right right'
'menu footer footer footer footer footer';
grid-gap: 10px;
background-color: #2196F3;
padding: 10px;
}
.grid-container > div {
background-color: rgba(255, 255, 255, 0.8);
text-align: center;
padding: 20px 0;
font-size: 30px;
}
.flex-container {
display: flex;
background-color: DodgerBlue;
}
.flex-container > div {
background-color: #f1f1f1;
margin: 10px;
padding: 20px;
font-size: 30px;
}
</style>
<script>
var r = Math.round(Math.random()*255);
var g = Math.round(Math.random()*255);
var b = Math.round(Math.random()*255);
function changeColor(){
for (const elem of document.querySelectorAll('div')) {
r = Math.round(Math.random()*255);
g = Math.round(Math.random()*255);
b = Math.round(Math.random()*255);
elem.style.backgroundColor = "rgb("+r+","+g+","+b+", 0.8)";
}
var myTimer = setInterval(changeColor, 1000);
var running = true;
}
</script>
</head>
<body>
<h1>Grid Layout</h1>
<p>This grid layout contains six columns and three rows:</p>
<div class="flex-container">
<button onclick="changeColor()">GO!</button>
<p id="timeinfo">TIME</p>
</div>
<div class="grid-container">
<div class="item1">Header</div>
<div class="item2">Menu</div>
<div class="item3">Main</div>
<div class="item4">Right</div>
<div class="item5">Footer</div>
</div>
</body>
</html>
I'm lost in regards to that. Any insight would be appreciated. Thanks!
You need to take the setInterval() function out of the changeColor() function. You are creating a new interval with each subsequent changeColor() recursive call, and that is what's causing the speed up until we become epileptic. Plus add the more specific selector others have mentioned ('.grid-container > div'). This is something that is probably better done in a reactive style, rather than looping through an array of elements every second, but if you are just getting into html/js/css this is a pretty good little test. I would probably do something more like this:
var r = Math.round(Math.random()*255);
var g = Math.round(Math.random()*255);
var b = Math.round(Math.random()*255);
var myTimer;
function startSwitching() {
myTimer = setInterval(changeColor, 1000);
}
function stopSwitching() {
clearInterval(myTimer);
}
function changeColor(){
for (const elem of document.querySelectorAll('.grid-container > div')) {
r = Math.round(Math.random()*255);
g = Math.round(Math.random()*255);
b = Math.round(Math.random()*255);
elem.style.backgroundColor = "rgb("+r+","+g+","+b+", 0.8)";
}
}
You need to be careful using intervals and make sure you clean up after yourself, or you could crash the browser. Also, you didn't really ask a question, so it's unclear what exactly you are wanting to change.
Add document.querySelectorAll('.grid-container > div') in for loop, so that only divs inside grid-container will change color.
document.querySelectorAll('div') actually returns a list of elements. In order to change the styling, you'll want to iterate through the elements. Here's one way you could go about doing that:
for (const elem of document.querySelectorAll('.grid-container > div')) {
r = Math.round(Math.random()*255);
g = Math.round(Math.random()*255);
b = Math.round(Math.random()*255);
elem.style.backgroundColor = "rgb("+r+","+g+","+b+")";
}
Related
I have a contenteditable div and I want the site to count each row of text the user has in the div similar to many coding IDEs. (Example image below to show what I mean:)
How would I go about doing this?
You can accomplish this with two container elements and a little bit of scripting:
$(document).ready(function(){
$('#edit').css('min-height', $('#edit').height());
$('#edit').html('');
var currentHeight = $('#edit').height();
var lineHeight = currentHeight;
$('#edit').keyup(function(){
if($(this).height()!=currentHeight){
currentHeight = $(this).height();
var lines = currentHeight/lineHeight;
$('#nums').html('')
for (i = 1; i < lines+1; i++) {
$('#nums').append('<span>'+i+'</span>')
}
}
});
});
#container{
border: 2px solid gray;
display: flex;
width: 200px;
}
#nums{
width: 25px;
background-color: lightgrey;
}
#nums span{
width: 100%;
display: block;
text-align: center;
}
#edit{
display: inline-block;
width: 100%;
}
#editwrapper{
width: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="container">
<div id="nums">
<span>1</span>
</div>
<div id="editwrapper">
<div id="edit" contenteditable="true">
filler
</div>
</div>
</div>
That's not so simple, actually. If a user typed in your div and the text wrapped, JavaScript doesn't know that it wrapped; there's no row to count!
This solution is super specific, and I'll leave showing the line numbers to you:
<div contenteditable="true" id="myDiv" style="width:300px;font-family:monospace" onchange="myHandler()"></div>
<script>
const limit = 40 // number of monospace chars to fill a row
let count = 0
function myHandler (e) {
let lines = Math.ceil(e.target.value.length / limit)
if (lines !== count) {
showlines(lines) // your function to display the line #s
count = lines
}
}
</script>
Takeaway: your div should be a fixed width and you should count how many monospace chars fit within that width. Then display those lines.
I have this interactive grid and when I hover over the cell it colors the inside divs background color but its is coloring past the container its in, how do I get it to only color inside the container? it seems to stop at the containers top and sides but the bottom can be colored downwards the whole page. This is my code
***HTML**
<!DOCTYPE html>
<html>
<head>
<title> Claudias Etch-A-Sketch</title>
<link rel= "stylesheet" href="style.css">
</head>
<body>
<section class="back">
<h1>
<center> Claudia Etch-A-Sketch!</center>
</h1>
</section>
<section>
<center><div>
<button class="Black">Black</button>
<button class="Pink">Pink</button>
<button class="Clear">Eraser</button>
</div></center>
</section>
<section>
<center><div id="container"> </div></center>
</section>
</body>
<script src ="javascript.js"> </script>
</html>
**JAVASCRIPT**
const container = document.getElementById("container");
const btn = document.querySelector('button');
let y = document.querySelectorAll('button');
y.forEach(button => {
button.addEventListener('click', () => {
let choice = button.innerHTML;
switch (choice) {
case "Pink":
random();
break;
case "Black":
blackchange();
break;
case "Eraser":
reset();
break;
}
});
});
var currentMouseoverFunction = function() {};
function blackchange() {
container.removeEventListener('mouseover', currentMouseoverFunction);
currentMouseoverFunction = function(e) {
e.target.classList.remove('pink');
e.target.classList.remove('reset');
e.target.classList.add('black');
};
container.addEventListener('mouseover', currentMouseoverFunction);
};
function random() {
container.removeEventListener('mouseover', currentMouseoverFunction);
currentMouseoverFunction = function(e) {
e.target.classList.remove('black');
e.target.classList.remove('reset');
e.target.classList.add('pink');
};
container.addEventListener('mouseover', currentMouseoverFunction);
};
function reset() {
container.removeEventListener('mouseover', currentMouseoverFunction);
currentMouseoverFunction = function(e) {
e.target.classList.remove('black');
e.target.classList.remove('pink');
e.target.classList.add('reset');
};
container.addEventListener('mouseover', currentMouseoverFunction);
};
function makeRows(rows, cols) {
container.style.setProperty('--grid-rows', rows);
container.style.setProperty('--grid-cols', cols);
for (c = 0; c < (rows * cols); c++) {
let cell = document.createElement("div");
container.appendChild(cell).className = "griditem";
};
};
makeRows(24, 24);
**CSS**
#container{
width:500px;
height:500px;
margin-top: 30px;
margin-bottom: 300px;
background:#eee;
}
#container div {
position: center;
float:left;
height: 35px;
width: 35px
}
.black {
background: black;
}
.pink {
background: pink;
}
.reset {
background: transparent;
}
The divs in #container are floated left with float:left; in the CSS. Float takes elements out of the normal layout of the page, so the #container's height doesn't expand with its content. To fix this, remove the explicit height:500px; height definition of #container, and add a clear element after the .griditem divs inside #container.
const container = document.getElementById("container");
const btn = document.querySelector('button');
let y = document.querySelectorAll('button');
y.forEach(button => {
button.addEventListener('click', () => {
let choice = button.innerHTML;
switch (choice) {
case "Pink":
random();
break;
case "Black":
blackchange();
break;
case "Eraser":
reset();
break;
}
});
});
var currentMouseoverFunction = function() {};
function blackchange() {
container.removeEventListener('mouseover', currentMouseoverFunction);
currentMouseoverFunction = function(e) {
e.target.classList.remove('pink');
e.target.classList.remove('reset');
e.target.classList.add('black');
};
container.addEventListener('mouseover', currentMouseoverFunction);
};
function random() {
container.removeEventListener('mouseover', currentMouseoverFunction);
currentMouseoverFunction = function(e) {
e.target.classList.remove('black');
e.target.classList.remove('reset');
e.target.classList.add('pink');
};
container.addEventListener('mouseover', currentMouseoverFunction);
};
function reset() {
container.removeEventListener('mouseover', currentMouseoverFunction);
currentMouseoverFunction = function(e) {
e.target.classList.remove('black');
e.target.classList.remove('pink');
e.target.classList.add('reset');
};
container.addEventListener('mouseover', currentMouseoverFunction);
};
function makeRows(rows, cols) {
container.style.setProperty('--grid-rows', rows);
container.style.setProperty('--grid-cols', cols);
for (c = 0; c < (rows * cols); c++) {
let cell = document.createElement("div");
container.appendChild(cell).className = "griditem";
};
let cell = document.createElement("span");
container.appendChild(cell).style.display = "block";
cell.style.clear = "both";
};
makeRows(24, 24);
#container {
width: 500px;
margin-top: 30px;
margin-bottom: 300px;
background: #eee;
}
#container div {
position: center;
float: left;
height: 35px;
width: 35px
}
.black {
background: black;
}
.pink {
background: pink;
}
.reset {
background: transparent;
}
<section class="back">
<h1>
<center> Claudia Etch-A-Sketch!</center>
</h1>
</section>
<section>
<center>
<div>
<button class="Black">Black</button>
<button class="Pink">Pink</button>
<button class="Clear">Eraser</button>
</div>
</center>
</section>
<section>
<center>
<div id="container"> </div>
</center>
</section>
As a side note, your .griditem divs are 35px wide each, so you should make the #container 840px wide if you want this to be a 24x24 square grid.
Your code seems a little strange in that it uses some outdated solutions.
First, don't use <center>, it's obsolete. You should keep the appearance (including alignment) of your page inside the CSS.
Second, you don't have to use float (hardly ever). It is a common source of bugs like the one you're experiencing. Instead you could use flexbox, so your container would look like:
#container{
display: flex; /* Puts all children inside a flexbox */
flex-wrap: wrap; /* And allows the flexbox to span multiple lines */
margin-top: 30px;
margin-bottom: 300px;
background:#eee;
}
Now, for the children, this solution allows them to distort slightly to adjust to the width of the parent:
.griditem {
flex-basis: 35px; /* Set a base width */
flex: 1; /* Allows it to stretch */
height: 35px;
}
Flexbox will prove to be a very important tool in your toolset. Give it a go!
Edit: a simpler solution - sorry.
I'm usually so excited to tell people about flexbox that I sometimes overlook simpler solutions. As I see it, the reason you resorted to float is because you added a ton of divs and they stacked on top of each other - divs are "blocks" and blocks take up the hole line.
You could just have created them as spans, which as "inlines" concatenate one after the other (like letters) and do the line-break naturally. But then setting dimensions for them wouldn't work - they take their width from their content.
The simpler solution by far would be to give the divs the rule display: inline-block. Now they'd have the right size, would be inline and wrap nicely.
But then you'd find there's a mysterious space separating the lines, which is not margin nor padding. That's actually whitespace between the divs. Ugh!
The easiest way to fix that is giving the container font-size: 0 - the space is there, it just doesn't take up... space.
Anyway, the css would look like:
#container{
font-size: 0;
margin-top: 30px;
margin-bottom: 300px;
background:#eee;
}
.griditem {
display: inline-block;
height: 35px;
width: 35px;
}
I've two containers that move with a media query. When they move I use matchMedia to fire an event that causes a div to be moved from one container to the other.
CodePen
The problem comes when I refresh the page. The css media query works, but matchMedia only works when the query changes. If you reload the codepen at under 1000px width you'll see the problem.
I've had a search and read of a few answers, but I'm new to JS and wary of what I should and shouldn't call (especially when it comes to listening for size changes).
Should I just use enquire.js? I've read that it doesn't have this problem. Or is there an easy fix?
<header>
<div class="container" id="container1">
<div class="item">1</div>
<div class="item" id="moveMe">2</div>
<div class="item" id="item3">3</div>
</div>
<div class="container" id="container2">
<div class="item" id="item4">4</div>
</div>
</header>
css
header{
display: flex;
justify-content: space-around;
align-items: center;
width: 100%;
padding: 1rem;
background: #eaeaea;
}
#media (max-width: 1000px){
header{
flex-direction: column;
}
}
.container{
display: flex;
padding: 2rem;
}
#container1{
background: red;
}
#container2{
background: blue;
}
.item{
padding: 2rem;
margin: 2rem;
background: white;
}
js
var container1 = document.getElementById('container1');
var container2 = document.getElementById('container2');
var moveMe = document.getElementById('moveMe');
var item3 = document.getElementById('item3');
var item4 = document.getElementById('item4');
matchMedia("(max-width: 1000px)").addListener(function(mql){
if(mql.matches){
container2.insertBefore(moveMe, item4);
} else {
container1.insertBefore(moveMe, item3);
}
})
*edit: RE i.terrible's suggestion I tried to split up the code so I could run it on window.onload.
var mql = window.matchMedia("(max-width: 1000px)").addListener(checkMedia);
window.onload = checkMedia(mql);
function checkMedia(mql){
if(mql.matches){
container2.insertBefore(moveMe, item4);
} else {
container1.insertBefore(moveMe, item3);
}
}
it didn't work though...
*edit2: Ah...this works. I messed up above.
var mql = window.matchMedia("(max-width: 1000px)");
checkMedia(mql);
mql.addListener(checkMedia);
function checkMedia(mql){
if(mql.matches){
container2.insertBefore(moveMe, item4);
} else {
container1.insertBefore(moveMe, item3);
}
}
I hope I understand you correctly. You'd like to react to changes in the screen width. To do that, bind the media query to a resize listener like this:
window.addEventListener('resize', function(){
<your mq-code here>
});
If you want the MQ to fire when the page loads, also run it on load as well:
window.onload = <your code>
In any case it would be better to refactor your media query to a separate function.
Please ask, if I should elaborate.
This worked for me:
var container1 = document.getElementById('container1');
var container2 = document.getElementById('container2');
var moveMe = document.getElementById('moveMe');
var item3 = document.getElementById('item3');
var item4 = document.getElementById('item4');
var mql = window.matchMedia("(max-width: 1000px)");
checkMedia(mql);
mql.addListener(checkMedia);
function checkMedia(mql){
if(mql.matches){
container2.insertBefore(moveMe, item4);
} else {
container1.insertBefore(moveMe, item3);
}
}
My code is the following :
<!DOCTYPE html>
<html>
<style>
#1 { grid-area: 1; }
#2 { grid-area: 2; }
.grid-container {
display: grid;
grid-gap: 10px;
background-color: #2196F3;
padding: 10px;
}
.grid-container > div {
background-color: rgba(255, 255, 255, 0.8);
text-align: center;
padding: 20px 0;
font-size: 30px;
}
</style>
<div class="grid-container">
<button id="1" onclick="myFunction('1')">1</button>
<button id="2" onclick="myFunction('2')">2</button>
</div>
<script>
function myFunction(id)
{
if(document.getElementById(id).style.grid-area != "1")
{
document.getElementById(id).style.grid-area = "1";
if(id == 1)
{
document.getElementById("2").style.grid-area = "2";
}
else
{
document.getElementById("1").style.grid-area = "2";
}
}
}
</script>
</html>
I'm looking to change the position of a button on click to occupy the top position on the grid (swapping with the other button if the latter was the one initially on top). I learned that the position is determined by the CSS property "grid-area" but the problem is that it can't be accessed via javascript as it would seem. So, is there a way to achieve the same purpose?
a bit late, hope you will find it usfull:
Getting grid coordinates:
let btn = document.getElementById("1")
let x = btn.style.gridColumn
let y = btn.style.gridRow
Setting grid coordinates:
let btn=document.getElementById("2")
btn.style.gridColumn = x
btn.style.gridRow = y
replacing buttons entire grid area
If your button takes more than a single grid segment you can use gridArea like so:
let btn1=document.getElementById("1")
let btn2=document.getElementById("2")
btn2.style.gridArea = btn1.style.gridArea
I have 2 boxes and a vertical div line in one unique container div (code and fiddle below).
I'm using CSS grids to position my elements inside the container
What I'd like to accomplish is to use the vertical line to resize horizontally the two boxes based on the position of the vertical line.
I apologize if the question is noobish, I am new to web development, only used Python before, already tried to google and stackoverflow search but all solutions seem overly complicated and generally require additional libraries, I was looking for something simpler and JS only.
HTML:
<div class="wrapper">
<div class="box a">A</div>
<div class="handler"></div>
<div class="box b">B</div>
</div>
CSS:
body {
margin: 40px;
}
.wrapper {
display: grid;
grid-template-columns: 200px 8px 200px;
grid-gap: 10px;
background-color: #fff;
color: #444;
}
.box {
background-color: #444;
color: #fff;
border-radius: 5px;
padding: 20px;
font-size: 150%;
resize: both;
}
.handler{
width: 3px;
height: 100%;
padding: 0px 0;
top: 0;
background: red;
draggable: true;
}
https://jsfiddle.net/gv8Lwckh/6/
What you intend to do can be done using CSS flexbox—there is no need to use CSS grid. The bad news is that HTML + CSS is not so smart that declaring resize and draggable will make the layout flexible and adjustable by user interaction. For that, you will have to use JS. The good news is that this is actually not too complicated.
Here is a quick screen grab of output the code below:
However, for you to understand the code I will post below, you will have to familiarize yourself with:
Event binding using .addEventListener. In this case, we will use a combination of mousedown, mouseup and mousemove to determine whether the user is in the middle of dragging the element
CSS flexbox layout
Description of the solution
Initial layout using CSS
Firstly, you will want to layout your boxes using CSS flexbox. We simply declare display: flex on the parent, and then use flex: 1 1 auto (which translates to "let the element grow, let the element shrink, and have equal widths). This layout is only valid at the initial rendering of the page:
.wrapper {
/* Use flexbox */
display: flex;
}
.box {
/* 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 auto;
}
Listen to drag interaction
You want to listen to mouse events that might have originated from your .handler element, and you want a global flag that remembers whether the user is dragging or not:
var handler = document.querySelector('.handler');
var isHandlerDragging = false;
Then you can use the following logic to check if the user is dragging or not:
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;
}
// Set boxA width properly
// [...more logic here...]
});
document.addEventListener('mouseup', function(e) {
// Turn off dragging flag when user mouse is up
isHandlerDragging = false;
});
Computing the width of box A
All you are left with now is to compute the width of box A (to be inserted in the [...more logic here...] placeholder in the code above), so that it matches that of the movement of the mouse. Flexbox will ensure that box B will fill up the remaining space:
// Get offset
var containerOffsetLeft = wrapper.offsetLeft;
// Get x-coordinate of pointer relative to container
var pointerRelativeXpos = e.clientX - containerOffsetLeft;
// 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
boxA.style.width = (pointerRelativeXpos - 8) + 'px';
boxA.style.flexGrow = 0;
Working example
var handler = document.querySelector('.handler');
var wrapper = handler.closest('.wrapper');
var boxA = 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
boxA.style.width = (Math.max(boxAminWidth, pointerRelativeXpos - 8)) + 'px';
boxA.style.flexGrow = 0;
});
document.addEventListener('mouseup', function(e) {
// Turn off dragging flag when user mouse is up
isHandlerDragging = false;
});
body {
margin: 40px;
}
.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 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;
}
<div class="wrapper">
<div class="box">A</div>
<div class="handler"></div>
<div class="box">B</div>
</div>
Here's an example of the drag event handling, but using CSS Grids
The trick is to set the grid-template-columns (or rows) on the grid container rather than than the size of the grid items
let isLeftDragging = false;
let isRightDragging = false;
function ResetColumnSizes() {
// when page resizes return to default col sizes
let page = document.getElementById("pageFrame");
page.style.gridTemplateColumns = "2fr 6px 6fr 6px 2fr";
}
function SetCursor(cursor) {
let page = document.getElementById("page");
page.style.cursor = cursor;
}
function StartLeftDrag() {
// console.log("mouse down");
isLeftDragging = true;
SetCursor("ew-resize");
}
function StartRightDrag() {
// console.log("mouse down");
isRightDragging = true;
SetCursor("ew-resize");
}
function EndDrag() {
// console.log("mouse up");
isLeftDragging = false;
isRightDragging = false;
SetCursor("auto");
}
function OnDrag(event) {
if (isLeftDragging || isRightDragging) {
// console.log("Dragging");
//console.log(event);
let page = document.getElementById("page");
let leftcol = document.getElementById("leftcol");
let rightcol = document.getElementById("rightcol");
let leftColWidth = isLeftDragging ? event.clientX : leftcol.clientWidth;
let rightColWidth = isRightDragging ? page.clientWidth - event.clientX : rightcol.clientWidth;
let dragbarWidth = 6;
let cols = [
leftColWidth,
dragbarWidth,
page.clientWidth - (2 * dragbarWidth) - leftColWidth - rightColWidth,
dragbarWidth,
rightColWidth
];
let newColDefn = cols.map(c => c.toString() + "px").join(" ");
// console.log(newColDefn);
page.style.gridTemplateColumns = newColDefn;
event.preventDefault()
}
}
#page {
height: 100%;
background-color: pink;
display: grid;
grid-template-areas: 'header header header header header' 'leftcol leftdragbar tabs tabs tabs' 'leftcol leftdragbar tabpages rightdragbar rightcol' 'leftcol leftdragbar footer footer footer';
grid-template-rows: min-content 1fr 9fr 1fr;
grid-template-columns: 2fr 6px 6fr 6px 2fr;
}
/*****************************/
#header {
background-color: lightblue;
overflow: auto;
grid-area: header;
}
#leftcol {
background-color: #aaaaaa;
overflow: auto;
grid-area: leftcol;
}
#leftdragbar {
background-color: black;
grid-area: leftdragbar;
cursor: ew-resize;
}
#tabs {
background-color: #cccccc;
overflow: auto;
grid-area: tabs;
}
#tabpages {
background-color: #888888;
overflow: auto;
grid-area: tabpages;
}
#rightdragbar {
background-color: black;
grid-area: rightdragbar;
cursor: ew-resize;
}
#rightcol {
background-color: #aaaaaa;
overflow: auto;
grid-area: rightcol;
}
#footer {
background-color: lightblue;
overflow: auto;
grid-area: footer;
}
<body onresize="ResetColumnSizes()">
<div id="page" onmouseup="EndDrag()" onmousemove="OnDrag(event)">
<div id="header">
Header
</div>
<div id="leftcol">
Left Col
</div>
<div id="leftdragbar" onmousedown="StartLeftDrag()"></div>
<div id="tabs">
Tabs
</div>
<div id="tabpages">
Tab Pages
</div>
<div id="rightdragbar" onmousedown="StartRightDrag()"></div>
<div id="rightcol">
Rightcol
</div>
<div id="footer">
Footer
</div>
</div>
</body>
https://codepen.io/lukerazor/pen/GVBMZK
I changed, so you can add more Horizontal and Vertical slider.
test1.html:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="test1.css">
<script src= "test1.js" > </script>
</head>
<body>
<div id="page" onmouseup="EndDrag()" onmousemove="OnDrag(event)">
<div id="header">
Header asdlkj flkdfj sdflkksdjf sd;flsdjf sd;flkjsd;fljsd;flsdj;fjsd f;sdlfj;sdlfj
</div>
<div id="leftcol">
Left Col
</div>
<div id="leftdragbar" onmousedown="StartHDrag(1)"></div>
<div id="tabs">
Tabs
</div>
<div id="topdragbar" onmousedown="StartVDrag(2)"></div>
<div id="tabpages">
Tab Pages
</div>
<div id="rightdragbar" onmousedown="StartHDrag(3)"></div>
<div id="rightcol">
Rightcol
</div>
<div id="botdragbar" onmousedown="StartVDrag(4)"></div>
<div id="footer">
Footer
</div>
</div>
<div id= 'status'></div>
</body>
</html>
test1.css
body {
}
#page {
height: 100vh;
background-color: pink;
display: grid;
grid-template-areas:
'header header header header header'
'leftcol leftdragbar tabs tabs tabs'
'leftcol leftdragbar topdragbar topdragbar topdragbar'
'leftcol leftdragbar tabpages rightdragbar rightcol'
'botdragbar botdragbar botdragbar botdragbar botdragbar'
'footer footer footer footer footer';
grid-template-rows: min-content 1fr 6px 9fr 6px 1fr;
grid-template-columns: 2fr 6px 6fr 6px 2fr;
}
/*****************************/
#header {
background-color: lightblue;
overflow: auto;
grid-area: header;
}
#leftcol {
background-color: #aaaaaa;
overflow: auto;
grid-area: leftcol;
}
#leftdragbar {
background-color: black;
grid-area: leftdragbar;
cursor: ew-resize;
}
#topdragbar {
background-color: black;
grid-area: topdragbar;
cursor: ns-resize;
}
#botdragbar {
background-color: black;
grid-area: botdragbar;
cursor: ns-resize;
}
#tabs {
background-color: #cccccc;
overflow: auto;
grid-area: tabs;
}
#tabpages {
background-color: #888888;
overflow: auto;
grid-area: tabpages;
}
#rightdragbar {
background-color: black;
grid-area: rightdragbar;
cursor: ew-resize;
}
#rightcol {
background-color: #aaaaaa;
overflow: auto;
grid-area: rightcol;
}
#footer {
background-color: lightblue;
overflow: auto;
grid-area: footer;
}
test1.js
let isHDragging = false;
let isVDragging = false;
let cols = ['2fr','6px','6fr','6px','2fr']; //grid-template-columns: 2fr 6px 6fr 6px 2fr;
let colns = ['leftcol','','tabpages','','rightcol'];
let Tcols = [];
let rows = ['min-content','1fr','6px','9fr','6px','1fr']; //grid-template-rows: min-content 1fr 6px 9fr 1fr
let rowns = ['header','tabs','','tabpages','','footer'];
let Trows = []
let CLfactor ;
let CRfactor ;
let gWcol = -1;
let gWrow = -1;
function StartHDrag(pWcol) {
isHDragging = true;
SetCursor("ew-resize");
CLfactor = parseFloat(cols[pWcol-1]) / document.getElementById(colns[pWcol-1]).clientWidth;
CRfactor = parseFloat(cols[pWcol+1]) / document.getElementById(colns[pWcol+1]).clientWidth;
Tcols = cols.map(parseFloat);
gWcol = pWcol;
}
function StartVDrag(pRow) {
isVDragging = true;
SetCursor("ns-resize");
CLfactor = parseFloat(rows[pRow-1]) / document.getElementById(rowns[pRow-1]).clientHeight;
CRfactor = parseFloat(rows[pRow+1]) / document.getElementById(rowns[pRow+1]).clientHeight;
Trows = rows.map(parseFloat);
gWrow = pRow;
}
function SetCursor(cursor) {
let page = document.getElementById("page");
page.style.cursor = cursor;
}
function EndDrag() {
isHDragging = false;
isVDragging = false;
SetCursor("auto");
}
function OnDrag(event) {
if(isHDragging) {
Tcols[gWcol-1] += (CLfactor * event.movementX);
Tcols[gWcol+1] -= (CLfactor * event.movementX);
cols[gWcol-1] = Math.max(Tcols[gWcol-1],0.01) + "fr";
cols[gWcol+1] = Math.max(Tcols[gWcol+1],0.01) + "fr";
let newColDefn = cols.join(" ");
page.style.gridTemplateColumns = newColDefn;
} else if (isVDragging) {
Trows[gWrow-1] += (CLfactor * event.movementY);
Trows[gWrow+1] -= (CLfactor * event.movementY);
rows[gWrow-1] = Math.max(Trows[gWrow-1],0.01) + "fr";
rows[gWrow+1] = Math.max(Trows[gWrow+1],0.01) + "fr";
let newRowDefn = rows.join(" ");
page.style.gridTemplateRows = newRowDefn;
document.getElementById("footer").innerHTML = newRowDefn;
}
event.preventDefault()
}
To actually match the question! Making a dragbar to resize divs inside CSS grids.
Here is a possible way, the original OP layout is kept, as well as the CSS, using Grids.
The goal is to capture the original state of the Grid Template Columns, and convert it to floats.
The browser always compute in pixels, and the sum of those columns + the gap, represent the total width of the container element. That sum must always be the same, or the elements will jump!
NB: Calls to .getComputedStyle() are not very efficient, optimisation is likely possible here!
Notice, doing this way using grids and screenX avoid the common jumping bug on mouse down.
Comments are added, this will allow to apply the logic with any number of columns, or rows, good luck.
With the usage of pointer events, it does works from a touch device as well.
let target = document.querySelector("div") // Target container element
let md = false; // Will be true at mouse down
let xorigin; // Click origin X position
let gtcorigin = []; // Origin Grid Template Columns in pixels
const pointerdown = (e) => {
if (e.target.classList[0] === "handler"){ // Filter to target the wanted element
md = true; // Set mouse down
xorigin = e.screenX; // Store the origin X position
// Grid Template Columns, array of pixels as float
gtcorigin = window.getComputedStyle(target)["grid-template-columns"].split(" ").map((a) => +(a.slice(0, -2)));
document.body.style.cursor = "col-resize" // This makes things nice
document.body.style.userSelect = "none" // This makes things nice
}
}
const pointerup = (e) => {
md = false; // Reset bool at mouse up
document.body.style.cursor = "pointer"
document.body.style.userSelect = "unset"
}
const resizer = (e) => {
if (md){ // Mouse is down hover the handler element
let gtc = window.getComputedStyle(target)["grid-template-columns"].split(" ").map((a) => +(a.slice(0, -2))); // Grid Template Columns, array of pixels as float
let xdragdif = xorigin - e.screenX; // Move in pixels since the click
gtc[0] = gtcorigin[0] - xdragdif // First column, if negative, it will grow
gtc[2] = gtcorigin[2] + xdragdif // Third column
gtc = gtc.map((a) => a+"px") // Set back the values in string with "px"
document.querySelector("console").textContent = gtc.join(" ") // !!! This is only for the demo
target.style.gridTemplateColumns = gtc.join(" ") // Apply the new Grid Template Column as inline style.
}
}
// Attach all events on the largest container element. Here the body is used.
document.body.addEventListener("pointerdown", pointerdown, false)
document.body.addEventListener("pointerup", pointerup, false)
document.body.addEventListener("pointermove", resizer, false)
body {
margin: 40px;
overflow-x: hidden
}
.wrapper {
display: grid;
grid-template-columns: 200px 8px 200px;
grid-gap: 10px;
background-color: #fff;
color: #444;
}
.box {
background-color: #444;
color: #fff;
border-radius: 5px;
padding: 20px;
font-size: 150%;
}
.handler{
width: 3px;
height: 100%;
padding: 0px 0;
top: 0;
background: red;
cursor: col-resize
}
<div class="wrapper">
<div class="box">A</div>
<div class="handler"></div>
<div class="box">B</div>
</div>
<console></console>
No limits are applied here, this can be enhanced with CSS only, using min-width and other similar rules, and the float values can be retrieved to create range sliders and more, this way.