I'm trying to design a component in which you could change the width proportions of two blocks by moving a slider left and right:
codpen and demo:
.outer {
display: flex;
flex-direction: row;
}
.block {
height: 200px;
width: -webkit-calc(50% - 5px);
width: -moz-calc(50% - 5px);
width: calc(50% - 5px);
}
.block-1 {
background-color: red;
}
.block-2 {
background-color: green;
}
.slider {
line-height: 100%;
width: 10px;
background-color: #dee2e6;
border: none;
cursor: e-resize;
}
<div id="app">
<div class="outer">
<div class="block block-1">
Block 1
</div>
<div class="slider">
S<br>l<br>i<br>d<br>e<br>r
</div>
<div class="block block-2">
Block 2
</div>
</div>
</div>
I have tried using draggable-vue-directive and change the width of blocks based on slider position.
However, it didn't work really well, since draggable-vue-directive set the slider to position:fixed which in turn messed up the block alignment.
How can I make the .slider block horizontally draggable without setting position:fixed?
How to correctly resize Block1 and Block2 when slider moves?
Note: I'm not using jQuery
You can adjust your flexbox along with resize - the downside is that the slider its not very customizeable:
add resize: horizontal to one of the flex items
add flex: 1 to the other flex item (so that this flex item will adjust automatically in response to the changing width of the other flex item as it is resized)
See demo below:
.outer {
display: flex;
flex-direction: row;
}
.block {
height: 100px;
width: 50%; /* 50% would suffice*/
}
.block-1 {
background-color: red;
resize: horizontal; /* resize horizontal */
overflow: hidden; /* resize works for overflow other than visible */
}
.block-2 {
background-color: green;
flex: 1; /* adjust automatically */
}
<div id="app">
<div class="outer">
<div class="block block-1">
Block 1
</div>
<div class="block block-2">
Block 2
</div>
</div>
</div>
So we'll use vanilla JS instead of the resize solution above:
use a mousedown listener that registers a mousemove listener that updates the block-1 width (and reset the mouseup event)
also consider min-width: 0 to override min-width: auto of the block-2 element
See demo below:
let block = document.querySelector(".block-1"),
slider = document.querySelector(".slider");
slider.onmousedown = function dragMouseDown(e) {
let dragX = e.clientX;
document.onmousemove = function onMouseMove(e) {
block.style.width = block.offsetWidth + e.clientX - dragX + "px";
dragX = e.clientX;
}
// remove mouse-move listener on mouse-up
document.onmouseup = () => document.onmousemove = document.onmouseup = null;
}
.outer {
display: flex;
flex-direction: row;
}
.block {
height: 100px;
width: 50%; /* 50% would suffice*/
}
.block-1 {
background-color: red;
}
.block-2 {
background-color: green;
flex: 1; /* adjust automatically */
min-width: 0; /* allow flexing beyond auto width */
overflow: hidden; /* hide overflow on small width */
}
.slider {
line-height: 100%;
width: 10px;
background-color: #dee2e6;
border: none;
cursor: col-resize;
user-select: none; /* disable selection */
text-align: center;
}
<div id="app">
<div class="outer">
<div class="block block-1">
Block 1
</div>
<div class="slider">
S<br>l<br>i<br>d<br>e<br>r
</div>
<div class="block block-2">
Block 2
</div>
</div>
</div>
Solution
You can adapt the above into Vue easily without using any custom Vue plugins for this - the changes are:
#mousedown listener on slider that triggers the slider
use of refs to update the width of block-1
See demo below:
new Vue({
el: '#app',
data: {
block1W: '50%'
},
methods: {
drag: function(e) {
let dragX = e.clientX;
let block = this.$refs.block1;
document.onmousemove = function onMouseMove(e) {
block.style.width = block.offsetWidth + e.clientX - dragX + "px";
dragX = e.clientX;
}
// remove mouse-move listener on mouse-up
document.onmouseup = () => document.onmousemove = document.onmouseup = null;
}
}
});
.outer {
display: flex;
flex-direction: row;
}
.block {
height: 100px;
width: 50%; /* 50% would suffice*/
}
.block-1 {
background-color: red;
}
.block-2 {
background-color: green;
flex: 1; /* adjust automatically */
min-width: 0; /* allow flexing beyond auto width */
overflow: hidden; /* hide overflow on small width */
}
.slider {
line-height: 100%;
width: 10px;
background-color: #dee2e6;
border: none;
cursor: col-resize;
user-select: none; /* disable selection */
text-align: center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="outer">
<div class="block block-1" ref="block1" :style="{'width': block1W}">
Block 1
</div>
<div class="slider" #mousedown="drag">
S<br>l<br>i<br>d<br>e<br>r
</div>
<div class="block block-2">
Block 2
</div>
</div>
</div>
Related
I'm trying to make it so that when you click on the button with the class .booking__button, the block scrolls up under the header. Position should not change, only scroll. This is done so that the search results of the booking module, which, would be visible to the user. I found the code that does the scrolling, but it works with the exact number, now 100px, but you understand that this distance will be different for everyone, depending on the height of the screen.
document.querySelector('.booking__button').addEventListener('click', () => {
window.scrollTo(0, 100);
});
body {
margin: 0;
}
.header {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 60px;
background: #002164;
}
.hero {
min-height: calc(100vh - 100px);
background: #fff;
}
.booking__module {
display: flex;
justify-content: center;
align-items: center;
background: #BC0B3C;
}
.booking__search {
height: 600px;
background: #ccc;
}
.booking__button {
height: 20px;
margin: 40px;
}
.others {
height: 200vh;
}
<header class="header"></header>
<main class="hero"></main>
<section class="booking">
<div class="booking__module">
<button class="booking__button">booking</button>
</div>
<div class="booking__search"></div>
</section>
<section class="others"></section>
One approach is below, with explanatory comments in the code. Note that while I changed the background-color of the <header> element, that's simply to visualise the functionality and is not at all required:
// we pass a reference to the Event Object ('evt') to the function:
document.querySelector('.booking__button').addEventListener('click', (evt) => {
// we retrieve the closest ancestor <section> element of the element
// to which the event-handler is bound, and retrieve the 'top' property
// of its bounding-client rect:
let {top} = evt.currentTarget.closest('section').getBoundingClientRect();
// we then scroll to that value:
window.scrollTo(0, top);
});
body {
margin: 0;
}
.header {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 60px;
/*background: #002164;*/
background-color: hsl(200deg 70% 70% / 0.4);
}
.hero {
min-height: calc(100vh - 100px);
background: #fff;
}
.booking__module {
display: flex;
justify-content: center;
align-items: center;
background: #BC0B3C;
}
.booking__search {
height: 600px;
background: #ccc;
}
.booking__button {
height: 20px;
margin: 40px;
}
.others {
height: 200vh;
}
<header class="header"></header>
<main class="hero"></main>
<section class="booking">
<div class="booking__module">
<button class="booking__button">booking</button>
</div>
<div class="booking__search"></div>
</section>
<section class="others"></section>
JS Fiddle demo.
References:
Element.closest().
Element.getBoundingClientRect().
Event.
Event.currentTarget.
EventTarget.addEventListener().
Window.scrollTo.
I am trying to fit a slide nav on the right side of the splitter, which is able to split when dragging the separator based on the width of the right side. https://jsfiddle.net/74bewsdu/
I tried modifying the width to auto as well as the z-index, it still doesn't work well.
index
<div class="splitter">
<div id="first">
<iframe src="{{ route('child') }}" style="width:100%; height:100%" frameBorder="0">
Your browser isn't compatible
</iframe>
</div>
<div id="separator"></div>
<div id="second">
<div id="mySidenav" class="sidenav"></div>
<div style="font-size:30px;cursor:pointer" onclick="openNav()">☰ open</div>
</div>
</div>
</div>
javascript
function openNav() {
document.getElementById("mySidenav").style.width = "250px";
}
css
.sidenav {
height: 100%;
width: 0;
position: fixed;
z-index: 1;
top: 0;
right: 0;
background-color: #111;
overflow-x: hidden;
transition: 0.5s;
padding-top: 60px;
}
You could get the offsetWidth of the right side div (#second) plus the offsetWidth of the separator, and set the sliding nav width to that value. If you don't want to completely overlap the menu button, you could subtract it by the width of the menu button (or some arbitrary constant).
Example (see comments in lines between /* added */ and /* end added */ areas in the snippet below). Update: I also added code to check to see if the slider nav is open. Now, if you drag the slider when the side nav is open, it should be dynamically resized.
let navIsOpen = false; // variable to keep track of if the slider nav is open
function openNav() {
/* added */
// calculate width of right side div + separator
let width = second.offsetWidth + separator.offsetWidth;
// uncomment below line to subtract a value so the slider doesn't obscure the menu button/text
//width = width - 95;
document.getElementById("mySidenav").style.width = width + "px";
navIsOpen = true; // slider nav is open
/* end added */
}
// function is used for dragging and moving
function dragElement(element, direction) {
var md; // remember mouse down info
const first = document.getElementById("first");
const second = document.getElementById("second");
element.onmousedown = onMouseDown;
element.onmousedown = onMouseDown;
function onMouseDown(e) {
//console.log("mouse down: " + e.clientX);
md = {
e,
offsetLeft: element.offsetLeft,
offsetTop: element.offsetTop,
firstWidth: first.offsetWidth,
secondWidth: second.offsetWidth
};
first.style.pointerEvents = 'none';
document.onmousemove = onMouseMove;
document.onmouseup = () => {
//console.log("mouse up");
document.onmousemove = document.onmouseup = null;
}
}
function onMouseMove(e) {
//console.log("mouse move: " + e.clientX);
var delta = {
x: e.clientX - md.e.clientX,
y: e.clientY - md.e.clientY
};
if (direction === "H") // Horizontal
{
// prevent negative-sized elements
delta.x = Math.min(Math.max(delta.x, -md.firstWidth),
md.secondWidth);
element.style.left = md.offsetLeft + delta.x + "px";
first.style.width = (md.firstWidth + delta.x) + "px";
second.style.width = (md.secondWidth - delta.x) + "px";
}
/* added */
if (navIsOpen) { // check if slider nav is open, if so resize
openNav();
}
/* end added */
}
}
dragElement(document.getElementById("separator"), "H");
.splitter {
width: 100%;
height: 300px;
display: flex;
}
#separator {
cursor: col-resize;
background-color: #aaa;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='30'><path d='M2 0 v30 M5 0 v30 M8 0 v30' fill='none' stroke='black'/></svg>");
background-repeat: no-repeat;
background-position: center;
width: 10px;
height: 100%;
/* prevent browser's built-in drag from interfering */
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
#first {
background-color: #dde;
width: 20%;
height: 100%;
min-width: 10px;
}
#second {
background-color: #eee;
width: 80%;
height: 100%;
min-width: 10px;
}
.sidenav {
height: 100%;
width: 0;
position: fixed;
z-index: 1;
top: 0;
right: 0;
background-color: #111;
overflow-x: hidden;
transition: 0.5s;
padding-top: 60px;
}
<div class="splitter">
<div id="first">
<iframe src="{{ route('child') }}" style="width:100%; height:100%" frameBorder="0">
Your browser isn't compatible
</iframe>
</div>
<div id="separator"></div>
<div id="second">
<div id="mySidenav" class="sidenav"></div>
<div style="font-size:30px;cursor:pointer" onclick="openNav()">☰ open</div>
</div>
</div>
We have a container (red), and a child div (blue). On increasing the child div's width, I want the child div to come to the next row instead of continuing on the same row.This is what is happening now :
This is what is needed :
.container{
width:100px;
height:50px;
background:red;
position:absolute;
}
.child{
width:120px;
height:20px;
background:blue;
margin:5px 5px 0px 5px;
position: relative;
}
<div class="container">
<div class="child"></div>
</div>
Any solution to the above stated issue will be very helpful :)
Thanks in advance!
You will not be able to do that with a single rectangular div but you can do something like this.
Hope this helps.
This is another solution not sure if it fits in your case.
.parent {
width: 500px;
display: grid;
grid-template-columns: repeat(auto-fill, 186px);
background-color: red;
}
.parent>* {
background-color: blue;
height: 50px;
border: 1px solid white;
color: white;
}
<div class="parent">
<div class="child">1</div>
<div class="child">2</div>
<div class="child">3</div>
<div class="child">4</div>
</div>
I have no idea to the bigger picture of your problem. However here is a hackish way to do it using HTML, CSS and Javascript.
What the javscript does is:
Get the width of the container and the child
Check if the child has a higher width than the container
If the above is true, get the offset (childWidth - containerWidth)
Reduce the child's width and make it equal to the container
Create a div and give it a width equal to the offset and give it a height equal to the child.
Append the div to the container
var container = document.querySelector('.container');
var containerWidth = container.offsetWidth;
var child = document.querySelector('.child');
var childWidth = child.offsetWidth;
(function() {
extendDivToNextRow();
})();
function extendDivToNextRow() {
let offset;
let secondRow;
if (childWidth > containerWidth) {
sliceChildDiv();
extendToSecondRow();
}
}
function getChildOffset() {
return childWidth - containerWidth;
}
function sliceChildDiv() {
const newWidth = childWidth - getChildOffset();
child.setAttribute("style", `width:${newWidth}px`);
}
function extendToSecondRow() {
const secondRow = createSecondRow();
container.appendChild(secondRow);
}
function createSecondRow() {
const row = document.createElement('div');
row.setAttribute("style", `width: ${getChildOffset()}px; height: 20px; background: blue`);
return row;
}
* {
margin: 0px;
}
.container {
width: 100px;
height: 50px;
background: red;
position: absolute;
}
.child {
width: 120px;
height: 20px;
background: blue;
position: relative;
}
<div class="container">
<div class="child"></div>
</div>
Here is the code snippet:
var wrapper = document.createElement('DIV');
wrapper.setAttribute("width", x * rows);
wrapper.setAttribute("height", y * columns);
wrapper.align = "center";
var buttonWrap = document.createElement('DIV');
buttonWrap.setAttribute("style", "clear:float");
As you can see in my code snippet, I have tried to center my div. But this code doesn't work. What works is making both divs fixed. But at the end of the day, the second div will then be upon the first div.
Please help.
If you can use only CSS I would do it this way:
.container {
width: 500px;
height: 150px;
border: solid black 1px;
/* Align center */
display: flex;
justify-content: center;
align-items: center;
}
.small {
background-color: red;
width: 100px;
height: 30px;
}
.big {
background-color: green;
width: 100px;
height: 100px;
}
<div class="container">
<div class="small"></div>
<div class="big"></div>
</div>
If you want to do it in javascript, apply the style written above in CSS this way:
var container = document.getElementsByClassName('container')[0];
container.style.display = "flex";
and so on...
This question already has an answer here:
CSS square with dynamic height
(1 answer)
Closed 5 years ago.
How can I resize a div to be the largest possible square within its container using CSS? If it is not possible with CSS, how can it be done with JavaScript?
If the container has height > width I would like the size of the square to width x width. If the container has width > height I would like the size the square to be height x height.
When the dimensions of the container changes the dimensions of the child should adjust accordingly.
I found this answer to be helpful to maintain the aspect ratio of the child. This approach doesn't work when the width of the container is larger than the height as the child overflows the parent as demonstrated in the following snippet.
.flex {
display: flex;
}
.wide,
.tall {
flex: none;
border: 3px solid red;
}
.wide {
width: 150px;
height: 100px;
}
.tall {
width: 100px;
height: 150px;
}
div.stretchy-wrapper {
width: 100%;
padding-bottom: 100%;
position: relative;
background: blue;
}
div.stretchy-wrapper>div {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
color: white;
font-size: 24px;
text-align: center;
}
<div class="flex">
<div class="wide">
<div class="stretchy-wrapper">
<div>Wide container</div>
</div>
</div>
<div class="tall">
<div class="stretchy-wrapper">
<div>Tall container</div>
</div>
</div>
</div>
Get width and height of all .stretchy-wrapper and parent of the same using map().
Now using a for loop assign max value to it parent.
Then $(window).resize call resizeDiv function whenever browser window size changes.
$(document).ready (function () {
function resizeDiv () {
var stretchyWrapper = $(".stretchy-wrapper"),
sWrapperWidth = stretchyWrapper.map (function () {
return $(this).width ();
}),
sWrapperHeight = stretchyWrapper.map (function () {
return $(this).height ();
}),
container = stretchyWrapper.map (function () {
return $(this).parent ();
});
for (var i in container) {
var maxVal = Math.max (sWrapperWidth[i], sWrapperHeight[i]);
$(container[i]).css ({"width": maxVal, "height": maxVal});
}
}
resizeDiv ();
$(window).resize (function () {
resizeDiv ();
});
});
.flex {
display: flex;
}
.wide,
.tall {
flex: none;
border: 3px solid red;
}
.wide {
width: 150px;
height: 100px;
}
.tall {
width: 100px;
height: 150px;
}
div.stretchy-wrapper {
width: 100%;
padding-bottom: 100%;
position: relative;
background: blue;
}
div.stretchy-wrapper>div {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
color: white;
font-size: 24px;
text-align: center;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="flex">
<div class="wide">
<div class="stretchy-wrapper">
<div>Wide container</div>
</div>
</div>
<div class="tall">
<div class="stretchy-wrapper">
<div>Tall container</div>
</div>
</div>
</div>