Check if element is partially in viewport - javascript

I'm trying to determine if an element is partially or fully in the viewport.
I've found this which will determine if an element is fully in view but kept getting confused when trying to determine partial visibility. I don't want to use jQuery.
Basically, the idea is that there will be an element on the page that could be out of view. Once the user scrolls that element into view, even partially, it should trigger an event. I'll handle the event trigger by binding an onscroll event. I just need the detection to work properly.
function isInViewport(element) {
var rect = element.getBoundingClientRect();
var html = document.documentElement;
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || html.clientHeight) &&
rect.right <= (window.innerWidth || html.clientWidth)
);
}
Any help would be greatly appreciated!

Late answer, but about a month ago I wrote a function that does exactly that, it determines how much an element is visible measured in percent in the viewport. Ive tested it in chrome, firefox, ie11, ios on iphone/ipad. The function returns true when X percent (as a number from 0 to 100) of the element is visible. Only determines if the measurements of the element are visible and not if the element is hidden with opacity, visibility etc..
const isElementXPercentInViewport = function(el, percentVisible) {
let
rect = el.getBoundingClientRect(),
windowHeight = (window.innerHeight || document.documentElement.clientHeight);
return !(
Math.floor(100 - (((rect.top >= 0 ? 0 : rect.top) / +-rect.height) * 100)) < percentVisible ||
Math.floor(100 - ((rect.bottom - windowHeight) / rect.height) * 100) < percentVisible
)
};

You need a solution based on element.offsetTop, element.offsetLeft, element.offsetHeight, element.offsetWidth, window.innerWidth and window.innerHeight
(depending on the situation, you might also want to take the scrolling position into consideration)
function isInViewport(element){
if(element.offsetTop<window.innerHeight &&
element.offsetTop>-element.offsetHeight
&& element.offsetLeft>-element.offsetWidth
&& element.offsetLeft<window.innerWidth){
return true;
} else {
return false;
}
}
function test(){
alert(isInViewport(document.getElementById("elem"))?"Yes":"No");
}
#elem{width: 20px; height: 20px; background: red; }
#elem{position: absolute;top: -9px;left: 600px;}
<div id="elem"></div>
<button onclick="test()">Check</button>

function partInViewport(elem) {
let x = elem.getBoundingClientRect().left;
let y = elem.getBoundingClientRect().top;
let ww = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
let hw = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
let w = elem.clientWidth;
let h = elem.clientHeight;
return (
(y < hw &&
y + h > 0) &&
(x < ww &&
x + w > 0)
);
}
document.addEventListener("scroll", ()=>{
let el = document.getElementById("test");
if (partInViewport(el)) {
document.getElementById("container").style.backgroundColor = "green";
} else {
document.getElementById("container").style.backgroundColor = "red";
}
});
#test {
height: 200px;
width: 145px;
background-color: grey;
}
#container {
height: 400px;
width: 345px;
transform: translate(400px, 360px);
background-color: red;
display: grid;
align-items: center;
justify-items: center;
}
body {
height: 1500px;
width: 1500px;
}
<div id="container">
<div id="test"></div>
</div>
My example for this code:
https://jsfiddle.net/xqpebwtv/27/

The modern way on how to handle this would be Intersection Observer (IO). With IO you can observe (as the name suggest) elements and trigger actions whenver an alement comes into view. You can set the percentages at which the observer is triggered (e.g. 10% in view, 90% in view, ... )
I really like this example from the linked page, there you have 4 different elements. Each with a different trigger percentage.
let observers = [];
startup = () => {
let wrapper = document.querySelector(".wrapper");
// Options for the observers
let observerOptions = {
root: null,
rootMargin: "0px",
threshold: []
};
// An array of threshold sets for each of the boxes. The
// first box's thresholds are set programmatically
// since there will be so many of them (for each percentage
// point).
let thresholdSets = [
[],
[0.5],
[0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0],
[0, 0.25, 0.5, 0.75, 1.0]
];
for (let i = 0; i <= 1.0; i += 0.01) {
thresholdSets[0].push(i);
}
// Add each box, creating a new observer for each
for (let i = 0; i < 4; i++) {
let template = document.querySelector("#boxTemplate").content.cloneNode(true);
let boxID = "box" + (i + 1);
template.querySelector(".sampleBox").id = boxID;
wrapper.appendChild(document.importNode(template, true));
// Set up the observer for this box
observerOptions.threshold = thresholdSets[i];
observers[i] = new IntersectionObserver(intersectionCallback, observerOptions);
observers[i].observe(document.querySelector("#" + boxID));
}
// Scroll to the starting position
document.scrollingElement.scrollTop = wrapper.firstElementChild.getBoundingClientRect().top + window.scrollY;
document.scrollingElement.scrollLeft = 750;
}
intersectionCallback = (entries) => {
entries.forEach((entry) => {
let box = entry.target;
let visiblePct = (Math.floor(entry.intersectionRatio * 100)) + "%";
box.querySelector(".topLeft").innerHTML = visiblePct;
box.querySelector(".topRight").innerHTML = visiblePct;
box.querySelector(".bottomLeft").innerHTML = visiblePct;
box.querySelector(".bottomRight").innerHTML = visiblePct;
});
}
startup();
body {
padding: 0;
margin: 0;
}
svg:not(:root) {
display: block;
}
.playable-code {
background-color: #f4f7f8;
border: none;
border-left: 6px solid #558abb;
border-width: medium medium medium 6px;
color: #4d4e53;
height: 100px;
width: 90%;
padding: 10px 10px 0;
}
.playable-canvas {
border: 1px solid #4d4e53;
border-radius: 2px;
}
.playable-buttons {
text-align: right;
width: 90%;
padding: 5px 10px 5px 26px;
}
.contents {
position: absolute;
width: 700px;
height: 1725px;
}
.wrapper {
position: relative;
top: 600px;
}
.sampleBox {
position: relative;
left: 175px;
width: 150px;
background-color: rgb(245, 170, 140);
border: 2px solid rgb(201, 126, 17);
padding: 4px;
margin-bottom: 6px;
}
#box1 {
height: 300px;
}
#box2 {
height: 175px;
}
#box3 {
height: 350px;
}
#box4 {
height: 100px;
}
.label {
font: 14px "Open Sans", "Arial", sans-serif;
position: absolute;
margin: 0;
background-color: rgba(255, 255, 255, 0.7);
border: 1px solid rgba(0, 0, 0, 0.7);
width: 3em;
height: 18px;
padding: 2px;
text-align: center;
}
.topLeft {
left: 2px;
top: 2px;
}
.topRight {
right: 2px;
top: 2px;
}
.bottomLeft {
bottom: 2px;
left: 2px;
}
.bottomRight {
bottom: 2px;
right: 2px;
}
<template id="boxTemplate">
<div class="sampleBox">
<div class="label topLeft"></div>
<div class="label topRight"></div>
<div class="label bottomLeft"></div>
<div class="label bottomRight"></div>
</div>
</template>
<main>
<div class="contents">
<div class="wrapper">
</div>
</div>
</main>

What your code is saying is that:
The top side of the element must be below the top side of the window,
The left of the element must be to the right of the left side of the window,
The bottom side of the element must be to the top of the bottom side of the window, AND
The right side of the element must be to the left of the right side of the window
What you want:
The top side of the element must be below the top side of the window OR the bottom side of the element must be above the bottom side of the window, AND
The left side of the element must be to the right of the left side of the window OR the right side of the element must be to the left of the right side of the window
Take what you will from that, the code should be simple enough from here.

This should do it, offsets are not needed, since we are comparing client rectangles.
function isPartiallyVisibleInViewport(element, viewport) {
var bound = element.getBoundingClientRect();
var bound2 = viewport.getBoundingClientRect();
return bound.bottom > bound2.top && bound.top < bound2.bottom;
}
This function only checks vertically and must be extended if you also want to check horizontally:
return bound.bottom > bound2.top && bound.top < bound2.bottom && bound.right > bound2.left && bound.left < bound2.right;

Related

Check if a piece of the element is in viewport

I am working on a Javascript code for checking if element is in viewport.
But now I have a code that returns only true if the element is 100% in the viewport.
Is there a way for example if there are 10 pixels returns true or if a percentage... of the element is in the viewport return true?
My code for so far
<script type="text/javascript">
var elem = document.getElementById("result");
var bounding = elem.getBoundingClientRect();
if (bounding.top >= 0 && bounding.left >= 0 && bounding.right <= window.innerWidth && bounding.bottom <= window.innerHeight) {
alert('in viewport');
}
</script>
Based on #Jorg's code, here's the same with the Intersection Observer API, which is a newer way of checking for intersections. This will work on all modern browsers ~ 93.5% according to Can I Use
This is set up to make it consider anything that's 50% within the viewport as within the threshold. I made it such a large value so it's easy to see how it works.
As you'll notice with this, the callback is only called at the threshold (after the initial check). So, if you want an accurate intersection percentage, you'll probably want to increase the number of thresholds checked.
let callback = (entries, observer) => {
entries.forEach(entry => {
entry.target.style.backgroundColor = entry.isIntersecting ? 'green' : 'red';
entry.target.innerHTML = entry.intersectionRatio;
})
}
let observer = new IntersectionObserver(callback, {
threshold: [0.5] // If 50% of the element is in the screen, we count it!
// Can change the thresholds based on your needs. The default is 0 - it'll run only when the element first comes into view
});
['div1', 'div2', 'div3', 'div4'].forEach(d => {
const div = document.getElementById(d);
if (div) observer.observe(div);
})
html,
body {
padding: 0;
margin: 0;
}
body {
height: 200vh;
width: 200vw;
}
#div1 {
position: absolute;
left: calc(100vw - 60px - 10px);
top: 10px;
height: 100px;
width: 60px;
background-color: red;
color: white;
}
#div2 {
position: absolute;
left: 20px;
top: 10px;
height: 50px;
width: 60px;
background-color: blue;
color: white;
}
#div3 {
position: absolute;
left: calc(100vw - 260px + 50px);
top: max(calc(100vh - 350px + 120px), 120px);
height: 350px;
width: 260px;
background-color: green;
color: white;
text-align: left;
}
#div4 {
position: absolute;
height: 9000px;
width: 9000px;
color: black;
background-color: yellow;
}
<div id="div1"></div>
<div id="div2"></div>
<div id="div3"></div>
<!-- enable this div to see an example of a div LARGER than your viewport. -->
<!-- <div id="div4"></div> -->
I guess what you're looking for is the intersection between the element and the viewport? Meaning, find out how much of the div overlaps with the viewport.
Using the function below should tell you, between 0 and 1, how much of the DIV fits inside the viewport. Be aware though, that the div could also just be larger than the viewport this way, in which case the overlapping area is also less than 1.
Here is a working example
const intersection = (r1, r2) => {
const xOverlap = Math.max(0, Math.min(r1.x + r1.w, r2.x + r2.w) - Math.max(r1.x, r2.x));
const yOverlap = Math.max(0, Math.min(r1.y + r1.h, r2.y + r2.h) - Math.max(r1.y, r2.y));
const overlapArea = xOverlap * yOverlap;
return overlapArea;
}
const percentInView = (div) => {
const rect = div.getBoundingClientRect();
const dimension = { x: rect.x, y: rect.y, w: rect.width, h: rect.height };
const viewport = { x: 0, y: 0, w: window.innerWidth, h: window.innerHeight };
const divsize = dimension.w * dimension.h;
const overlap = intersection(dimension, viewport);
return overlap / divsize;
}

Vertical dragBar for resizing two divs

I wanted a vertical dragBar for resizing two divs. I have created an example for the same but I am facing an issue.
Actual : As and when I resize the the upper div and move the slider down, the area of parent div increases and hence a scroll bar is given.
Expected: When Resizing, if the slider is moved down, it should only show the data contained in the upper div and when slider is moved up, it should show the content of lower div and should not increase the over all length of the parent div.
var handler = document.querySelector('.handler');
var wrapper = handler.closest('.wrapper');
var boxA = wrapper.querySelector('.box1');
var boxB = wrapper.querySelector('.box2');
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 containerOffsetTop= wrapper.offsetTop;
var containerOffsetBottom= wrapper.offsetBottom;
// Get x-coordinate of pointer relative to container
var pointerRelativeXpos = e.clientY - containerOffsetTop;
var pointerRelativeXpos2 = e.clientY - e.offsetTop + e.offsetHeight;
var boxAminWidth = 30;
boxA.style.height = (Math.max(boxAminWidth, pointerRelativeXpos - 2)) + 'px';
boxA.style.flexGrow = 0;
boxB.style.height = (Math.max(boxAminWidth, pointerRelativeXpos2 - 8)) + 'px';
boxB.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 */
}
.box1, .box2 {
background-color: #444;
color: #fff;
border-radius: 5px;
padding: 20px;
font-size: 150%;
margin-top:2%;
/* 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 */
}
.handler {
width: 20px;
height:7px;
padding: 0;
cursor: ns-resize;
}
.handler::before {
content: '';
display: block;
width: 100px;
height: 100%;
background: red;
margin: 0 auto;
}
<div class="wrapper">
<div class="box1">A</div>
<div class="handler"></div>
<div class="box2">B</div>
</div>
Hope I was clear in explaining the issue I am facing in my project. Any help is appreciated.
It looks like your on the right track. You just need to make the wrapper a flexbox with the flex direction column and assign it a height. Also box 2 needs to have a flex of 1 so it can grow and shrink as needed. Finally I needed to remove the code that set the flex grow to 0 in the JavaScript. Here is the result.
var handler = document.querySelector('.handler');
var wrapper = handler.closest('.wrapper');
var boxA = wrapper.querySelector('.box1');
var boxB = wrapper.querySelector('.box2');
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;
}
e.preventDefault();
// Get offset
var containerOffsetTop= wrapper.offsetTop;
var containerOffsetBottom= wrapper.offsetBottom;
// Get x-coordinate of pointer relative to container
var pointerRelativeXpos = e.clientY - containerOffsetTop;
var pointerRelativeXpos2 = e.clientY - e.offsetTop + e.offsetHeight;
var boxAminWidth = 30;
boxA.style.height = (Math.max(boxAminWidth, pointerRelativeXpos - 2)) + 'px';
boxB.style.height = (Math.max(boxAminWidth, pointerRelativeXpos2 - 8)) + 'px';
});
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;
flex-direction: column;
height: 200px;
}
.box1, .box2 {
background-color: #444;
color: #fff;
border-radius: 5px;
padding: 20px;
font-size: 150%;
margin-top:2%;
/* 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 */
}
.box2 {
flex: 1;
}
.handler {
width: 20px;
height:7px;
padding: 0;
cursor: ns-resize;
}
.handler::before {
content: '';
display: block;
width: 100px;
height: 100%;
background: red;
margin: 0 auto;
}
<div class="wrapper">
<div class="box1">A</div>
<div class="handler"></div>
<div class="box2">B</div>
</div>

Slider handle needs to restricted inside the container, should not go beyond the container

In the custom slider i have created, the handle is moving beyond the container. But i want it to stay within the container limits. We could just do it simple by setting margin-left as offset in CSS. But My requirement is when the handle right end detect the container's end the handle should not be allowed to move anymore. Any help is appreciated. Thanks.
Demo Link: https://jsfiddle.net/mohanravi/1pbzdyyd/30/
document.getElementsByClassName('contain')[0].addEventListener("mousedown", downHandle);
function downHandle() {
document.addEventListener("mousemove", moveHandle);
document.addEventListener("mouseup", upHandle);
}
function moveHandle(e) {
var left = e.clientX - document.getElementsByClassName('contain')[0].getBoundingClientRect().left;
var num = document.getElementsByClassName('contain')[0].offsetWidth / 100;
var val = (left / num);
if (val < 0) {
val = 0;
} else if (val > 100) {
val = 100;
}
var pos = document.getElementsByClassName('contain')[0].getBoundingClientRect().width * (val / 100);
document.getElementsByClassName('bar')[0].style.left = pos + 'px';
}
function upHandle() {
document.removeEventListener("mousemove", moveHandle);
document.removeEventListener("mouseup", upHandle);
}
.contain {
height: 4px;
width: 450px;
background: grey;
position: relative;
top: 50px;
left: 40px;
}
.bar {
width: 90px;
height: 12px;
background: transparent;
border: 1px solid red;
position: absolute;
top: calc(50% - 7px);
left: 0px;
cursor: ew-resize;
}
<div class='contain'>
<div class='bar'></div>
</div>
You need to change
this
document.getElementsByClassName('bar')[0].style.left = pos + 'px';
to this
if(pos > 90){
document.getElementsByClassName('bar')[0].style.left = pos - 90 + 'px';
}
else{
document.getElementsByClassName('bar')[0].style.left = 0 + 'px';
}
since width of your bar is 90px I am subtracting 90.
See this updated fiddle

Animating newly created objects with different keyframes values

So I'm trying to make simple animation. When you press somewhere inside blue container, a circle should be created in this place and then go up. After some research I found how to put JS values into keyframes, but it's changing values for every object not just for freshly created. If you run snipped and press somewhere high and then somewhere low you will see what I'm talking about.
I found some AWESOME solution with Raphael library, but I'm a beginner and I'm trying to make something like this in JS. Is it even possible? How?
var bubble = {
posX: 0,
posY: 0,
size: 0
};
var aquarium = document.getElementById("container");
var ss = document.styleSheets;
var keyframesRule = [];
function findAnimation(animName) { //function to find keyframes and insert replace values in them
for (var i = 0; i < ss.length; i++) {
for (var j = 0; j < ss[i].cssRules.length; j++) {
if (window.CSSRule.KEYFRAMES_RULE == ss[i].cssRules[j].type && ss[i].cssRules[j].name == animName) {
keyframesRule.push(ss[i].cssRules[j]);
}
}
}
return keyframesRule;
}
function changeAnimation (nameAnim) { //changing top value to cursor position when clicked
var keyframesArr = findAnimation(nameAnim);
for (var i = 0; i < keyframesArr.length; i++) {
keyframesArr[i].deleteRule("0%");
keyframesArr[i].appendRule("0% {top: " + bubble.posY + "px}");
}
}
function createBubble(e) {
"use strict";
bubble.posX = e.clientX;
bubble.posY = e.clientY;
bubble.size = Math.round(Math.random() * 100);
var bubbleCircle = document.createElement("div");
aquarium.appendChild(bubbleCircle);
bubbleCircle.className = "bubble";
var bubbleStyle = bubbleCircle.style;
bubbleStyle.width = bubble.size + "px";
bubbleStyle.height = bubble.size + "px";
bubbleStyle.borderRadius = (bubble.size / 2) + "px";
//bubbleStyle.top = bubble.posY - (bubble.size / 2) + "px";
bubbleStyle.left = bubble.posX - (bubble.size / 2) + "px";
changeAnimation("moveUp");
bubbleCircle.className += " animate";
}
aquarium.addEventListener("click", createBubble);
//console.log(bubble);
body {
background-color: red;
margin: 0;
padding: 0;
}
#container {
width: 100%;
height: 100%;
position: fixed;
top: 80px;
left: 0;
background-color: rgb(20,255,200);
}
#surface {
width: 100%;
height: 40px;
position: fixed;
top: 40px;
opacity: 0.5;
background-color: rgb(250,250,250);
}
.bubble {
position: fixed;
border: 1px solid blue;
}
.animate {
animation: moveUp 5s linear;//cubic-bezier(1, 0, 1, 1);
-webkit-animation: moveUp 5s linear;//cubic-bezier(1, 0, 1, 1);
}
#keyframes moveUp{
0% {
top: 400px;
}
100% {
top: 80px;
}
}
#-webkit-keyframes moveUp{
0% {
top: 400px;
}
100% {
top: 80px;
}
}
<body>
<div id="container">
</div>
<div id="surface">
</div>
</body>
Here is a possible solution. What I did:
Remove your functions changeAnimation () and findAnimation() - we don't need them
Update the keyframe to look like - only take care for the 100%
#keyframes moveUp { 100% {top: 80px;} }
Assign top of the new bubble with the clientY value
After 5 seconds set top of the bubble to the offset of the #container(80px) - exactly when animation is over to keep the position of the bubble, otherwise it will return to initial position
var bubble = {
posX: 0,
posY: 0,
size: 0
};
var aquarium = document.getElementById("container");
function createBubble(e) {
"use strict";
bubble.posX = e.clientX;
bubble.posY = e.clientY;
bubble.size = Math.round(Math.random() * 100);
var bubbleCircle = document.createElement("div");
aquarium.appendChild(bubbleCircle);
bubbleCircle.className = "bubble";
var bubbleStyle = bubbleCircle.style;
bubbleStyle.width = bubble.size + "px";
bubbleStyle.height = bubble.size + "px";
bubbleStyle.borderRadius = (bubble.size / 2) + "px";
bubbleStyle.top = bubble.posY - (bubble.size / 2) + "px";
bubbleStyle.left = bubble.posX - (bubble.size / 2) + "px";
bubbleCircle.className += " animate";
// The following code will take care to reset top to the top
// offset of #container which is 80px, otherwise circle will return to
// the position of which it was created
(function(style) {
setTimeout(function() {
style.top = '80px';
}, 5000);
})(bubbleStyle);
}
aquarium.addEventListener("click", createBubble);
body {
background-color: red;
margin: 0;
padding: 0;
}
#container {
width: 100%;
height: 100%;
position: fixed;
top: 80px;
left: 0;
background-color: rgb(20, 255, 200);
}
#surface {
width: 100%;
height: 40px;
position: fixed;
top: 40px;
opacity: 0.5;
background-color: rgb(250, 250, 250);
}
.bubble {
position: fixed;
border: 1px solid blue;
}
.animate {
animation: moveUp 5s linear;
/*cubic-bezier(1, 0, 1, 1);*/
-webkit-animation: moveUp 5s linear;
/*cubic-bezier(1, 0, 1, 1);*/
}
#keyframes moveUp {
100% {
top: 80px;
}
}
#-webkit-keyframes moveUp {
100% {
top: 80px;
}
}
<body>
<div id="container"></div>
<div id="surface"></div>
</body>
The problem about your code was that it is globally changing the #keyframes moveUp which is causing all the bubbles to move.
The problem with your code is that you're updating keyframes which are applied to all bubbles. I tried another way of doing it by using transition and changing the top position after the element was added to the DOM (otherwise it wouldn't be animated).
The main problem here is to wait the element to be added to the DOM. I tried using MutationObserver but it seems to be called before the element is actually added to the DOM (or at least rendered). So the only way I found is using a timeout which will simulate this waiting, although there must be a better one (because it may be called too early, causing the bubble to directly stick to the top), which I would be happy to hear about.
var bubble = {
posX: 0,
posY: 0,
size: 0
};
var aquarium = document.getElementById("container");
function createBubble(e) {
"use strict";
bubble.posX = e.clientX;
bubble.posY = e.clientY;
bubble.size = Math.round(Math.random() * 100);
var bubbleCircle = document.createElement("div");
aquarium.appendChild(bubbleCircle);
bubbleCircle.classList.add("bubble");
var bubbleStyle = bubbleCircle.style;
bubbleStyle.width = bubble.size + "px";
bubbleStyle.height = bubble.size + "px";
bubbleStyle.borderRadius = (bubble.size / 2) + "px";
bubbleStyle.top = bubble.posY - (bubble.size / 2) + "px";
bubbleStyle.left = bubble.posX - (bubble.size / 2) + "px";
setTimeout(function() {
bubbleCircle.classList.add("moveUp");
}, 50);
}
aquarium.addEventListener("click", createBubble);
body {
background-color: red;
margin: 0;
padding: 0;
}
#container {
width: 100%;
height: 100%;
position: fixed;
top: 80px;
left: 0;
background-color: rgb(20, 255, 200);
}
#surface {
width: 100%;
height: 40px;
position: fixed;
top: 40px;
opacity: 0.5;
background-color: rgb(250, 250, 250);
}
.bubble {
position: fixed;
border: 1px solid blue;
transition: 5s;
}
.moveUp {
top: 80px !important;
}
<body>
<div id="container">
</div>
<div id="surface">
</div>
</body>
Also, I used the classList object instead of className += ... because it is more reliable.

Best way to do a split pane in HTML [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 2 years ago.
Improve this question
Is there a good technique to make a resizable split pane in HTML?
May it be done using CSS / jQuery / JavaScript or is there a good JavaScript library that have been used?
(An example of a split pane is the favorites bar in Internet Explorer which you may have docked to the left of your main browser window.)
I wanted a vanilla, lightweight (jQuery UI Layout weighs in at 185 KB), no dependency option (all existing libraries require jQuery), so I wrote Split.js.
It weights less than 2 KB and does not require any special markup. It supports older browsers back to Internet Explorer 9 (or Internet Explorer 8 with polyfills). For modern browsers, you can use it with Flexbox and grid layouts.
Simplest HTML + CSS accordion, with just CSS resize.
div {
resize: vertical;
overflow: auto;
border: 1px solid
}
.menu {
display: grid
/* Try height: 100% or height: 100vh */
}
<div class="menu">
<div>
Hello, World!
</div>
<div>
Hello, World!
</div>
<div>
Hello, World!
</div>
</div>
Simplest HTML + CSS vertical resizable panes:
div {
resize: horizontal;
overflow: auto;
border: 1px solid;
display: inline-flex;
height: 90vh
}
<div>
Hello, World!
</div>
<div>
Hello, World!
</div>
The plain HTML, details element!.
<details>
<summary>Morning</summary>
<p>Hello, World!</p>
</details>
<details>
<summary>Evening</summary>
<p>How sweat?</p>
</details>
Simplest HTML + CSS topbar foldable menu
div{
display: flex
}
summary,p{
margin: 0px 0 -1px 0px;
padding: 0 0 0 0.5rem;
border: 1px black solid
}
summary {
padding: 0 1rem 0 0.5rem
}
<div>
<details>
<summary>FILE</summary>
<p>Save</p>
<p>Save as</p>
</details>
<details>
<summary>EDIT</summary>
<p>Pump</p>
<p>Transfer</p>
<p>Review</p>
<p>Compile</p>
</details>
<details>
<summary>PREFERENCES</summary>
<p>How sweat?</p>
<p>Powered by HTML</p>
</details>
</div>
Fixed bottom menu bar, unfolding upward.
div{
display: flex;
position: fixed;
bottom: 0;
transform: rotate(180deg)
}
summary,p{
margin: 0px 0 -1px 0px;
padding: 0 0 0 0.5rem;
border: 1px black solid;
transform: rotate(180deg)
}
summary {
padding: 0 1rem 0 0.5rem;
}
<div>
<details>
<summary>FILE</summary>
<p>Save</p>
<p>Save as</p>
</details>
<details>
<summary>EDIT</summary>
<p>Pump</p>
<p>Transfer</p>
<p>Review</p>
<p>Compile</p>
</details>
<details>
<summary>PREF</summary>
<p>How?</p>
<p>Power</p>
</details>
</div>
Simplest HTML full-screen modal popup
.popup > p {
padding: 1rem;
margin: 0;
display: flex;
flex-direction: column;
width: 25vw
}
.popup summary {
padding: 1rem 0.5rem;
cursor: pointer;
max-height: 90vh;
overflow: auto
}
.popup[open] summary {
background: black;
color: white;
padding: 0.5rem;
}
.popup[open] {
position: fixed;
/* top: calc(50% - 25vw); */
left: calc(50% - 15vw);
outline: 5000px #00000090 solid;
border: 5px red solid;
border-radius: 0.5rem;
z-index: 1;
max-height: 90vh;
overflow-y: auto;
overflow-x: hidden
}
.popup[open] summary::after {
content: '❌';
float: right;
}
<details class="popup">
<summary>HTML popup</summary>
<p>
<span>Name</span>
<input value="HTML" />
<br>
<span>Difficulty</span>
<input type="number" value="3" />
<br>
<span>Coolness</span>
<input type="number" value="100" />
<br>
<p><span>Powered by HTML</span></p>
</p>
</details>
Simplest resizable pane, using JavaScript.
let ismdwn = 0
rpanrResize.addEventListener('mousedown', mD)
function mD(event) {
ismdwn = 1
document.body.addEventListener('mousemove', mV)
document.body.addEventListener('mouseup', end)
}
function mV(event) {
if (ismdwn === 1) {
pan1.style.flexBasis = event.clientX + "px"
} else {
end()
}
}
const end = (e) => {
ismdwn = 0
document.body.removeEventListener('mouseup', end)
rpanrResize.removeEventListener('mousemove', mV)
}
div {
display: flex;
border: 1px black solid;
width: 100%;
height: 200px;
}
#pan1 {
flex-grow: 1;
flex-shrink: 0;
flex-basis: 50%; /* initial status */
}
#pan2 {
flex-grow: 0;
flex-shrink: 1;
overflow-x: auto;
}
#rpanrResize {
flex-grow: 0;
flex-shrink: 0;
background: #1b1b51;
width: 0.2rem;
cursor: col-resize;
margin: 0 0 0 auto;
}
<div>
<div id="pan1">MENU</div>
<div id="rpanrResize"> </div>
<div id="pan2">BODY</div>
</div>
Improving on Reza's answer:
prevent the browser from interfering with a drag
prevent setting an element to a negative size
prevent drag getting out of sync with the mouse due to incremental delta interaction with element width saturation
<html><head><style>
.splitter {
width: 100%;
height: 100px;
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 the 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;
}
</style></head><body>
<div class="splitter">
<div id="first"></div>
<div id="separator" ></div>
<div id="second" ></div>
</div>
<script>
// A 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;
function onMouseDown(e)
{
//console.log("mouse down: " + e.clientX);
md = {e,
offsetLeft: element.offsetLeft,
offsetTop: element.offsetTop,
firstWidth: first.offsetWidth,
secondWidth: second.offsetWidth
};
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";
}
}
}
dragElement( document.getElementById("separator"), "H" );
</script></body></html>
I wrote simple code for it without any third-party library. This code is only for a horizontal splitter (vertical is the same).
function onload()
{
dragElement( document.getElementById("separator"), "H" );
}
// This function is used for dragging and moving
function dragElement( element, direction, handler )
{
// Two variables for tracking positions of the cursor
const drag = { x : 0, y : 0 };
const delta = { x : 0, y : 0 };
/* If present, the handler is where you move the DIV from
otherwise, move the DIV from anywhere inside the DIV */
handler ? ( handler.onmousedown = dragMouseDown ): ( element.onmousedown = dragMouseDown );
// A function that will be called whenever the down event of the mouse is raised
function dragMouseDown( e )
{
drag.x = e.clientX;
drag.y = e.clientY;
document.onmousemove = onMouseMove;
document.onmouseup = () => { document.onmousemove = document.onmouseup = null; }
}
// A function that will be called whenever the up event of the mouse is raised
function onMouseMove( e )
{
const currentX = e.clientX;
const currentY = e.clientY;
delta.x = currentX - drag.x;
delta.y = currentY - drag.y;
const offsetLeft = element.offsetLeft;
const offsetTop = element.offsetTop;
const first = document.getElementById("first");
const second = document.getElementById("second");
let firstWidth = first.offsetWidth;
let secondWidth = second.offsetWidth;
if (direction === "H" ) // Horizontal
{
element.style.left = offsetLeft + delta.x + "px";
firstWidth += delta.x;
secondWidth -= delta.x;
}
drag.x = currentX;
drag.y = currentY;
first.style.width = firstWidth + "px";
second.style.width = secondWidth + "px";
}
}
.splitter {
width: 500px;
height: 100px;
display: flex;
}
#separator {
cursor: col-resize;
background: url(https://raw.githubusercontent.com/RickStrahl/jquery-resizable/master/assets/vsizegrip.png) center center no-repeat #535353;
width: 10px;
height: 100px;
min-width: 10px;
}
#first {
background-color: green;
width: 100px;
height: 100px;
min-width: 10px;
}
#second {
background-color: red;
width: 390px;
height: 100px;
min-width: 10px;
}
<html>
<head>
<link rel="stylesheet" href="T10-Splitter.css">
<script src="T10-Splitter.js"></script>
</head>
<body onload="onload()">
<div class="splitter">
<div id="first"></div>
<div id="separator"></div>
<div id="second"></div>
</div>
</body>
</html>
Here is my lightweight vanilla JavaScript approach, using Flexbox:
http://codepen.io/lingtalfi/pen/zoNeJp
It was tested successfully in Google Chrome 54, Firefox 50, Safari 10, don't know about other browsers.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.rawgit.com/lingtalfi/simpledrag/master/simpledrag.js"></script>
<style type="text/css">
html, body {
height: 100%;
}
.panes-container {
display: flex;
width: 100%;
overflow: hidden;
}
.left-pane {
width: 18%;
background: #ccc;
}
.panes-separator {
width: 2%;
background: red;
position: relative;
cursor: col-resize;
}
.right-pane {
flex: auto;
background: #eee;
}
.panes-container,
.panes-separator,
.left-pane,
.right-pane {
margin: 0;
padding: 0;
height: 100%;
}
</style>
</head>
<body>
<div class="panes-container">
<div class="left-pane" id="left-pane">
<p>I'm the left pane</p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</div>
<div class="panes-separator" id="panes-separator"></div>
<div class="right-pane" id="right-pane">
<p>And I'm the right pane</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. A accusantium at cum cupiditate dolorum, eius eum
eveniet facilis illum maiores molestiae necessitatibus optio possimus sequi sunt, vel voluptate. Asperiores,
voluptate!
</p>
</div>
</div>
<script>
var leftPane = document.getElementById('left-pane');
var rightPane = document.getElementById('right-pane');
var paneSep = document.getElementById('panes-separator');
// The script below constrains the target to move horizontally between a left and a right virtual boundaries.
// - the left limit is positioned at 10% of the screen width
// - the right limit is positioned at 90% of the screen width
var leftLimit = 10;
var rightLimit = 90;
paneSep.sdrag(function (el, pageX, startX, pageY, startY, fix) {
fix.skipX = true;
if (pageX < window.innerWidth * leftLimit / 100) {
pageX = window.innerWidth * leftLimit / 100;
fix.pageX = pageX;
}
if (pageX > window.innerWidth * rightLimit / 100) {
pageX = window.innerWidth * rightLimit / 100;
fix.pageX = pageX;
}
var cur = pageX / window.innerWidth * 100;
if (cur < 0) {
cur = 0;
}
if (cur > window.innerWidth) {
cur = window.innerWidth;
}
var right = (100-cur-2);
leftPane.style.width = cur + '%';
rightPane.style.width = right + '%';
}, null, 'horizontal');
</script>
</body>
</html>
This HTML code depends on the simpledrag vanilla JavaScript lightweight library (less than 60 lines of code).
Hmm, I came across this property in CSS 3.
This might be easier to use.
CSS resize Property
In the old days, you would use frames to achieve this. There are several reasons why this approach is not so good. See Reece's response to Why are HTML frames bad?. See also Jakob Nielson's Why Frames Suck (Most of the Time).
A somewhat newer approach is to use inline frames. This has pluses and minuses as well: Are iframes considered 'bad practice'?
An even better approach is to use fixed positioning. By placing the navigation content (e.g. the favorites links in your example) in a block element (like a div) then applying position:fixed to that element and setting the left, top and bottom properties like this:
#myNav {
position: fixed;
left: 0px;
top: 0px;
bottom: 0px;
width: 200px;
}
... you will achieve a vertical column down the left side of the page that will not move when the user scrolls the page.
The rest of the content on the page will not "feel" the presence of this nav element, so it must take into account the 200px of space it occupies. You can do this by placing the rest for the content in another div and setting margin-left:200px;.
Many missed this post from Barguast on Feb 27 '15 where shows a interesting generic flexbox vertical and horizontal resizer.
Take a look: Flexbox Resizing
Barguast note that "... it only handles items sized with flex-grow. If flex-shrink or flex-basis is defined, then the calculations simply don't work.", and he is looking for a better solution, so do I.
Here is his code for reference:
function manageResize(md, sizeProp, posProp)
{
var r = md.target;
var prev = r.previousElementSibling;
var next = r.nextElementSibling;
if (!prev || !next) {
return;
}
md.preventDefault();
var prevSize = prev[sizeProp];
var nextSize = next[sizeProp];
var sumSize = prevSize + nextSize;
var prevGrow = Number(prev.style.flexGrow);
var nextGrow = Number(next.style.flexGrow);
var sumGrow = prevGrow + nextGrow;
var lastPos = md[posProp];
function onMouseMove(mm)
{
var pos = mm[posProp];
var d = pos - lastPos;
prevSize += d;
nextSize -= d;
if (prevSize < 0) {
nextSize += prevSize;
pos -= prevSize;
prevSize = 0;
}
if (nextSize < 0) {
prevSize += nextSize;
pos += nextSize;
nextSize = 0;
}
var prevGrowNew = sumGrow * (prevSize / sumSize);
var nextGrowNew = sumGrow * (nextSize / sumSize);
prev.style.flexGrow = prevGrowNew;
next.style.flexGrow = nextGrowNew;
lastPos = pos;
}
function onMouseUp(mu)
{
window.removeEventListener("mousemove", onMouseMove);
window.removeEventListener("mouseup", onMouseUp);
}
window.addEventListener("mousemove", onMouseMove);
window.addEventListener("mouseup", onMouseUp);
}
function setupResizerEvents()
{
document.body.addEventListener("mousedown", function (md) {
var target = md.target;
if (target.nodeType !== 1 || target.tagName !== "FLEX-RESIZER") {
return;
}
var parent = target.parentNode;
var h = parent.classList.contains("h");
var v = parent.classList.contains("v");
if (h && v) {
return;
} else if (h) {
manageResize(md, "scrollWidth", "pageX");
} else if (v) {
manageResize(md, "scrollHeight", "pageY");
}
});
}
setupResizerEvents();
flex {
display: flex;
}
flex-item > flex {
position: absolute;
width: 100%;
height: 100%;
}
flex.h {
-ms-flex-direction: row;
flex-direction: row;
}
flex.v {
-ms-flex-direction: column;
flex-direction: column;
}
flex-item {
display: flex;
position: relative;
overflow: hidden;
}
flex > flex-resizer {
-ms-flex: 0 0 8px;
flex: 0 0 8px;
background: white;
}
flex.h > flex-resizer {
cursor: ew-resize;
}
flex.v > flex-resizer {
cursor: ns-resize;
}
<body>
<flex class="v" style="height: 500px">
<flex-item style="flex: 1; background: red">Flex 1</flex-item>
<flex-resizer></flex-resizer>
<flex-item style="flex: 1; background: blue">
<flex class="h">
<flex-item style="flex: 1">Flex 2</flex-item>
<flex-resizer></flex-resizer>
<flex-item style="flex: 2; background: green">
<flex class="v">
<flex-item style="flex: 1; background: pink;">Flex 3</flex-item>
<flex-resizer></flex-resizer>
<flex-item style="flex: 1">
<flex class="h">
<flex-item style="flex: 1">Flex 4</flex-item>
<flex-resizer></flex-resizer>
<flex-item style="flex: 2; background: yellow">Flex 5</flex-item>
<flex-item style="flex: 2; background: yellow">Flex 6</flex-item>
</flex>
</flex-item>
</flex>
</flex-item>
</flex>
</flex-item>
</flex>
</body>
And here is my improved version:
function manageResize(md, sizeProp, posProp) {
var r = md.target;
var prev = r.previousElementSibling;
var next = r.nextElementSibling;
if (!prev || !next) {
return;
}
md.preventDefault();
var prevSize = prev[sizeProp];
var nextSize = next[sizeProp];
var sumSize = prevSize + nextSize;
var prevGrow = Number(prev.style.flexGrow);
var nextGrow = Number(next.style.flexGrow);
var sumGrow = prevGrow + nextGrow;
var lastPos = md[posProp];
function onMouseMove(mm) {
var pos = mm[posProp];
var d = pos - lastPos;
prevSize += d;
nextSize -= d;
if (prevSize < 0) {
nextSize += prevSize;
pos -= prevSize;
prevSize = 0;
}
if (nextSize < 0) {
prevSize += nextSize;
pos += nextSize;
nextSize = 0;
}
var prevGrowNew = sumGrow * (prevSize / sumSize);
var nextGrowNew = sumGrow * (nextSize / sumSize);
prev.style.flexGrow = prevGrowNew;
next.style.flexGrow = nextGrowNew;
lastPos = pos;
}
function onMouseUp(mu) {
// Change cursor to signal a state's change: stop resizing.
const html = document.querySelector('html');
html.style.cursor = 'default';
if (posProp === 'pageX') {
r.style.cursor = 'ew-resize';
} else {
r.style.cursor = 'ns-resize';
}
window.removeEventListener("mousemove", onMouseMove);
window.removeEventListener("mouseup", onMouseUp);
}
window.addEventListener("mousemove", onMouseMove);
window.addEventListener("mouseup", onMouseUp);
}
function setupResizerEvents() {
document.body.addEventListener("mousedown", function (md) {
// Used to avoid cursor's flickering
const html = document.querySelector('html');
var target = md.target;
if (target.nodeType !== 1 || target.tagName !== "FLEX-RESIZER") {
return;
}
var parent = target.parentNode;
var h = parent.classList.contains("h");
var v = parent.classList.contains("v");
if (h && v) {
return;
} else if (h) {
// Change cursor to signal a state's change: begin resizing on H.
target.style.cursor = 'col-resize';
html.style.cursor = 'col-resize'; // avoid cursor's flickering
// use offsetWidth versus scrollWidth (and clientWidth) to avoid splitter's jump on resize when a flex-item content overflow (overflow: auto).
manageResize(md, "offsetWidth", "pageX");
} else if (v) {
// Change cursor to signal a state's change: begin resizing on V.
target.style.cursor = 'row-resize';
html.style.cursor = 'row-resize'; // avoid cursor's flickering
manageResize(md, "offsetHeight", "pageY");
}
});
}
setupResizerEvents();
body {
/* margin:0; */
border: 10px solid #aaa;
}
flex {
display: flex;
overflow: hidden;
}
/* flex-item > flex {
position: absolute;
width: 100%;
height: 100%;
} */
flex.h {
flex-direction: row;
}
flex.v {
flex-direction: column;
}
flex-item {
/* display: flex; */
/* position: relative; */
/* overflow: hidden; */
overflow: auto;
}
flex > flex-resizer {
flex: 0 0 10px;
/* background: white; */
background-color: #aaa;
background-repeat: no-repeat;
background-position: center;
}
flex.h > flex-resizer {
cursor: ew-resize;
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>");
}
flex.v > flex-resizer {
cursor: ns-resize;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='30' height='10'><path d='M0 2 h30 M0 5 h30 M0 8 h30' fill='none' stroke='black'/></svg>");
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>flex-splitter</title>
<link rel="stylesheet" href="./src/styles.css">
<script src="./src/index.js" defer></script>
</head>
<body>
<flex class="v" style="flex: 1; height: 500px;">
<flex-item style="flex: 1;">Flex 1</flex-item>
<flex-resizer></flex-resizer>
<flex class="h" style="flex: 1;">
<flex-item style="flex: 1; background-color: aqua;">
<!--
The next section is an example to test the splitter when there is content inside a flex-item
-->
<section>
<div>
<label for="CursorCoor" style="display: block;">showCursorCoor: </label>
<textarea id="CursorCoor" rows="6" cols="50" wrap="soft" readonly></textarea>
</div>
<br />
<div>
<label for="boxInfo" style="display: block;">showBoxInfo: </label>
<textarea id="boxInfo" rows="6" cols="50" wrap="soft" readonly></textarea>
</div>
</section>
</flex-item>
<flex-resizer></flex-resizer>
<flex class="v" style="flex: 2; ">
<flex-item style="flex: 1; background: pink;">Flex 3</flex-item>
<flex-resizer></flex-resizer>
<flex class="h" style="flex: 1">
<flex-item style="flex: 1; background: green;">Flex 4</flex-item>
<flex-resizer></flex-resizer>
<flex-item style="flex: 2;">Flex 5</flex-item>
<!-- <flex-resizer></flex-resizer> -->
<flex-item style="flex: 3; background: darkorange;">Flex 6</flex-item>
</flex>
</flex>
</flex>
</flex>
</body>
</html>
Or see it on Codesandbox:
You can do it with jQuery UI without another JavaScript library. Just add a function to the .resizable resize event to adjust the width of the other div.
$("#left_pane").resizable({
handles: 'e', // 'East' side of div draggable
resize: function() {
$("#right_pane").outerWidth( $("#container").innerWidth() - $("#left_pane").outerWidth() );
}
});
Here's the complete JSFiddle.
One totally different approach is to put things in a grid, such as ui-grid or Kendo's grid, and have the columns be resizable. A downside is that users would not be able to resize the rows, though the row size could be set programmatically.
You can use absolute of fixed positioning. This CSS for example will dock a 2em-bar on the left side of your page:
body {
padding-left: 2.5em;
}
body > #bar {
position:fixed;
top:0; left:0;
width: 2em;
height: 100%;
border-right: 2px solid #55F; background: #ddd;
}
(Demo at jsfiddle.net)
The Angular version with no third-party libraries (based on personal_cloud's answer):
import { Component, Renderer2, ViewChild, ElementRef, AfterViewInit, OnDestroy } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements AfterViewInit, OnDestroy {
#ViewChild('leftPanel', {static: true})
leftPanelElement: ElementRef;
#ViewChild('rightPanel', {static: true})
rightPanelElement: ElementRef;
#ViewChild('separator', {static: true})
separatorElement: ElementRef;
private separatorMouseDownFunc: Function;
private documentMouseMoveFunc: Function;
private documentMouseUpFunc: Function;
private documentSelectStartFunc: Function;
private mouseDownInfo: any;
constructor(private renderer: Renderer2) {
}
ngAfterViewInit() {
// Init page separator
this.separatorMouseDownFunc = this.renderer.listen(this.separatorElement.nativeElement, 'mousedown', e => {
this.mouseDownInfo = {
e: e,
offsetLeft: this.separatorElement.nativeElement.offsetLeft,
leftWidth: this.leftPanelElement.nativeElement.offsetWidth,
rightWidth: this.rightPanelElement.nativeElement.offsetWidth
};
this.documentMouseMoveFunc = this.renderer.listen('document', 'mousemove', e => {
let deltaX = e.clientX - this.mouseDownInfo.e.x;
// set min and max width for left panel here
const minLeftSize = 30;
const maxLeftSize = (this.mouseDownInfo.leftWidth + this.mouseDownInfo.rightWidth + 5) - 30;
deltaX = Math.min(Math.max(deltaX, minLeftSize - this.mouseDownInfo.leftWidth), maxLeftSize - this.mouseDownInfo.leftWidth);
this.leftPanelElement.nativeElement.style.width = this.mouseDownInfo.leftWidth + deltaX + 'px';
});
this.documentSelectStartFunc = this.renderer.listen('document', 'selectstart', e => {
e.preventDefault();
});
this.documentMouseUpFunc = this.renderer.listen('document', 'mouseup', e => {
this.documentMouseMoveFunc();
this.documentSelectStartFunc();
this.documentMouseUpFunc();
});
});
}
ngOnDestroy() {
if (this.separatorMouseDownFunc) {
this.separatorMouseDownFunc();
}
if (this.documentMouseMoveFunc) {
this.documentMouseMoveFunc();
}
if (this.documentMouseUpFunc) {
this.documentMouseUpFunc();
}
if (this.documentSelectStartFunc()) {
this.documentSelectStartFunc();
}
}
}
.main {
display: flex;
height: 400px;
}
.left {
width: calc(50% - 5px);
background-color: rgba(0, 0, 0, 0.1);
}
.right {
flex: auto;
background-color: rgba(0, 0, 0, 0.2);
}
.separator {
width: 5px;
background-color: red;
cursor: col-resize;
}
<div class="main">
<div class="left" #leftPanel></div>
<div class="separator" #separator></div>
<div class="right" #rightPanel></div>
</div>
Running example on Stackblitz
I found a working splitter, http://www.dreamchain.com/split-pane/, which works with jQuery v1.9. Note I had to add the following CSS code to get it working with a fixed bootstrap navigation bar.
fixed-left {
position: absolute !important; /* to override relative */
height: auto !important;
top: 55px; /* Fixed navbar height */
bottom: 0px;
}
A good library is Shield UI - you can take a look at their flexible Splitter widget and the rest of the powerful components the framework offers.

Categories

Resources