CORE ISSUE: I need to properly establish the height of a child <div>
that is nested in a resizable parent <div>
. As the parent is resized, the child should not extend beyond the boundaries of the parent. The parent itself should be limited so as not to allow it to shrink beyond the minimal scrollable bounds of the child.
DEAD ENDS: Through hours of research, I have learned that CSS, even with the calc()
function, is incapable of setting the height and width of a <div>
dynamically. Also, it seems that in order to prevent a child <div>
from overflowing its parent's dimensions, the parent itself must have a fixed size. In the latter case, styles like height: auto; height: inherit;
and height: 100%;
might all yield the same results.
Similar questions have been asked here before, but none that I found addresses the resizing question. For example, the solution provided to the following question does not properly address my issue.
Prevent child div from overflowing parent div
In my situation, I have a <div>
that will be user-resized and/or dragged to any part of the screen. The contents of that <div>
should automatically scroll, if necessary. In no case should they exceed the dimensions of the container <div>
, the one that is resizable. My situation requires that I use no jQuery, but a JavaScript solution is welcome. (Even PERL might be worthwhile, as I will serve the page and update it via AJAX from a Perl platform.)
A fully-working example is included below which shows how the child <div>
exceeds its parent, even with the scrolling enabled, and this occurs regardless of how the parent <div>
is resized. (I have tested it in Firefox and Safari.)
Note that in my actual application, the <div>
contents will be supplied via AJAX per user selection from the <select>
menu, and the contents will have varying lengths. Regardless of the length returned, the container/parent <div>
should remain the same size as per the user's own adjustment.
<!DOCTYPE html>
<HTML lang="utf8">
<head>
<title>Example of Resizable/Draggable Container Div with Nested (Overflowing) Contents</title>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
<style type="text/css">
.nested {
background-color: #ffffcc;
overflow-y: auto;
overflow-x: hidden;
max-height: 720px;
height: inherit;
color: #000;
text-align: left;
font-weight: normal;
}
.resizable {
background: white;
min-width: 100px;
min-height: 120px;
width: 100%;
height: 100%;
position: absolute;
top: 80px;
left: 60px;
display: -webkit-box; /* OLD - iOS 6-, Safari 3.1-6, BB7 */
display: -ms-flexbox; /* TWEENER - IE 10 */
display: -webkit-flex; /* NEW - Safari 6.1+. iOS 7.1+, BB10 */
display: flex; /* NEW, Spec - Firefox, Chrome, Opera */
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
}
.resizable .resizers{
width: 100%;
height: 100%;
border: 3px solid #4286f4;
box-sizing: border-box;
}
.resizable .resizers .resizer{
width: 3px;
height: 5px;
border-radius: 50%; /*magic to turn square into circle*/
background: black;
border: 3px solid #4286f4;
position: absolute;
}
.resizable .resizers .resizer.top-left {
left: -2px;
top: -2px;
cursor: nwse-resize; /*resizer cursor*/
}
.resizable .resizers .resizer.top-right {
right: -2px;
top: -2px;
cursor: nesw-resize;
}
.resizable .resizers .resizer.bottom-left {
left: -2px;
bottom: -2px;
cursor: nesw-resize;
}
.resizable .resizers .resizer.bottom-right {
right: -2px;
bottom: -2px;
cursor: nwse-resize;
}
#dragselection {
position: absolute;
top: 6px;
left: 3%;
z-index: 8;
background-color: #f1f1f1;
border: 1px solid #d3d3d3;
display: -webkit-box; /* OLD - iOS 6-, Safari 3.1-6, BB7 */
display: -ms-flexbox; /* TWEENER - IE 10 */
display: -webkit-flex; /* NEW - Safari 6.1+. iOS 7.1+, BB10 */
display: flex; /* NEW, Spec - Firefox, Chrome, Opera */
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
}
.header {
padding: 4px;
margin-left: 2px;
margin-right: 2px;
margin-top: 4px;
margin-bottom: 4px;
text-align: center;
cursor: move;
z-index: 10;
background-color: #2168a8;
color: #fff;
font-size: 14px;
font-weight: bold;
}
#CV1 {
min-width:150px;
min-height:150px;
}
.top-banner {
display: inline-block;
margin-top: 0px;
margin-right: 7px;
margin-left: 5px;
margin-bottom: 7px;
background-color: #afb;
border-radius: 12px;
opacity: 0.9;
box-shadow: 3px 3px #878087;
max-width: 100%;
height: auto;
align: auto;
text-align: center;
}
</style>
</head>
<body onload="dragElement('dragselection');">
<div class="top-banner">
<p style="margin-top: 30px; margin-right: 60px; margin-left: 60px; margin-bottom: 30px; text-align:center; font-size: 2.0em; color:#fff;text-shadow: 2px 1px 2px #050050;">♰ Easy Text Reference ♰</p>
</div>
<form id="myform" name="nmyform" method="POST" accept-charset="utf-8" action="URL_for_CGI" >
<article>
<h1>Example:</h1>
<div id="flex-box" name="nflex-box" class="flex-container">
<div class="container" id="mainscreen" ondrop="drop(event)" ondragover="allowDrop(event)">
<div class="header flex-container-head" id="header-box" name="nheader-box">
<div class='resizable' id="dragselection" style="border: 1px solid gray; font-size:18px; height: 250px; ">
<div class='resizers'>
<div class='resizer top-left'></div>
<div class='resizer top-right'></div>
<div class='resizer bottom-left'></div>
<div class='resizer bottom-right'></div>
<div id="dragselectionheader" class='header'>
Your Text Selection
<select id="get_text" name="nget_text" onchange="getAjaxResult(['selection_text','recordnum'], 'POST'); ">
<option value="">---- Selection Menu ----</option>
<option value="1">Option 1</option>
<option value="2">Option 2</option>
<option value="3">Option 3</option>
</select>
</div>
<div id="CV1" class="nested">
A sample text:
In the annals of human history, the growth of nations, the rise and fall of empires, appear as if dependent on the will and prowess of man; the shaping of events seems, to a great degree, to be determined by his power, ambition, or caprice. But in the word of God the curtain is drawn aside, and we behold, above, behind, and through all the play and counterplay of human interest and power and passions, the agencies of the All-merciful One, silently, patiently working out the counsels of His own will. (Prophets and Kings, p. 499)
</div>
</div>
</div>
</div>
</div>
</div>
</article>
</form>
<script type="text/javascript">
/* Make the DIV element draggable: */
dragElement(document.getElementById(("dragselection")));
function dragElement(elmnt) {
var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
if (document.getElementById(elmnt.id + "header")) {
/* if present, the header is where you move the DIV from: */
document.getElementById(elmnt.id + "header").onmousedown = dragMouseDown;
} else {
/* otherwise, move the DIV from anywhere inside the DIV: */
elmnt.onmousedown = dragMouseDown;
}
function dragMouseDown(e) {
e = e || window.event;
/* get the mouse cursor position at startup: */
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
/* call a function whenever the cursor moves: */
document.onmousemove = elementDrag;
}
function elementDrag(e) {
e = e || window.event;
/* calculate the new cursor position: */
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
/* set the element's new position: */
if ((elmnt.offsetTop - pos2) < 0) {
elmnt.style.top = '0px'
} else {
elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
}
if ((elmnt.offsetLeft - pos1) < 0) {
elmnt.style.left = '0px'
} else {
elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
}
}
function closeDragElement() {
/* stop moving when mouse button is released:*/
document.onmouseup = null;
document.onmousemove = null;
}
}
/* Modeled after:
https://medium.com/the-z/making-a-resizable-div-in-js-is-not-easy-as-you-think-bda19a1bc53d
*/
function makeResizableDiv(div) {
const element = document.querySelector(div);
const resizers = document.querySelectorAll(div + ' .resizer')
const minimum_size = 120;
var original_width = 0;
var original_height = 0;
var original_x = 0;
var original_y = 0;
var original_mouse_x = 0;
var original_mouse_y = 0;
for (var i = 0;i < resizers.length; i++) {
const currentResizer = resizers[i];
currentResizer.addEventListener('mousedown', function(e) {
e.preventDefault()
original_width = parseFloat(getComputedStyle(element, null).getPropertyValue('width').replace('px', ''));
original_height = parseFloat(getComputedStyle(element, null).getPropertyValue('height').replace('px', ''));
original_x = element.getBoundingClientRect().left;
original_y = element.getBoundingClientRect().top;
original_mouse_x = e.pageX;
original_mouse_y = e.pageY;
window.addEventListener('mousemove', resize)
window.addEventListener('mouseup', stopResize)
})
function resize(e) {
if (currentResizer.classList.contains('bottom-right')) {
const width = original_width + (e.pageX - original_mouse_x);
const height = original_height + (e.pageY - original_mouse_y)
if (width > minimum_size) {
element.style.width = width + 'px'
element.style.left = original_x + 'px'
}
if (height > minimum_size) {
element.style.height = height + 'px'
element.style.top = original_y + 'px'
}
}
else if (currentResizer.classList.contains('bottom-left')) {
const height = original_height + (e.pageY - original_mouse_y)
const width = original_width - (e.pageX - original_mouse_x)
if (height > minimum_size) {
element.style.height = height + 'px'
element.style.top = original_y+'px'
}
if (width > minimum_size) {
element.style.width = width + 'px'
element.style.left = original_x+(e.pageX-original_mouse_x)+'px'
}
}
else if (currentResizer.classList.contains('top-right')) {
const width = original_width + (e.pageX - original_mouse_x)
const height = original_height - (e.pageY - original_mouse_y)
if (width > minimum_size) {
element.style.width = width + 'px'
element.style.left = original_x + 'px'
}
if (height > minimum_size) {
if (original_y+(e.pageY-original_mouse_y) < 0 ) {
element.style.top = '0px'
} else {
element.style.height = height + 'px'
element.style.top = original_y+(e.pageY-original_mouse_y)+'px'
}
}
}
else { /* top-left */
const width = original_width - (e.pageX - original_mouse_x)
const height = original_height - (e.pageY - original_mouse_y)
if (width > minimum_size) {
element.style.width = width + 'px'
element.style.left = original_x+(e.pageX-original_mouse_x)+'px'
}
if (height > minimum_size) {
if (original_y+(e.pageY-original_mouse_y) < 0 ) {
element.style.top = '0px'
} else {
element.style.height = height + 'px'
element.style.top = original_y+(e.pageY-original_mouse_y)+'px'
}
}
}
}
function stopResize() {
window.removeEventListener('mousemove', resize)
}
}
}
makeResizableDiv('.resizable')
</script>
</body>
</html>
UPDATES: I've adjusted the code above to reflect the suggestions provided so far. Unfortunately, I am unable, with this configuration, to limit the width of the parent. Anything less than 100% (or auto) is then inherited by the child, with unfavorable results. However, I do not wish for the parent to occupy the full width of the screen, so this solution is inadequate.
I have now figured out a workaround that might be used with Firefox. My Safari does not appear to accept flex
options, so there is still no cross-browser solution. For Firefox, the following JavaScript code can set the <div>
position after page load, as follows:
document.getElementById('dragselection').setAttribute("style","width:320px; height:240px; font-size:18px;");
I have now learned that the following code is more cross-browser compatible, and causes Safari to render it correctly.
display: -webkit-box; /* OLD - iOS 6-, Safari 3.1-6, BB7 */
display: -ms-flexbox; /* TWEENER - IE 10 */
display: -webkit-flex; /* NEW - Safari 6.1+. iOS 7.1+, BB10 */
display: flex; /* NEW, Spec - Firefox, Chrome, Opera */
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
I have updated that code in two places in the sample script. This may be workable at this point.