Chatbox component - Flex item with scrollable content [duplicate] - css

Here is an example chat app ->
The idea here is to have the .messages-container take up as much of the screen as it can. Within .messages-container, .scroll holds the list of messages, and in case there are more messages then the size of the screen, scrolls.
Now, consider this case:
The user scrolls to the bottom of the conversation
The .text-input, dynamically gets bigger
Now, instead of the user staying scrolled to the bottom of the conversation, the text-input increases, and they no longer see the bottom.
One way to fix it, if we are using react, calculate the height of text-input, and if anything changes, let .messages-container know
componentDidUpdate() {
window.setTimeout(_ => {
const newHeight = this.calcHeight();
if (newHeight !== this._oldHeight) {
this.props.onResize();
}
this._oldHeight = newHeight;
});
}
But, this causes visible performance issues, and it's sad to be passing messages around like this.
Is there a better way? Could I use css in such a way, to express that when .text-input-increases, I want to essentially shift up all of .messages-container

2:nd revision of this answer
Your friend here is flex-direction: column-reverse; which does all you ask while align the messages at the bottom of the message container, just like for example Skype and many other chat apps do.
.chat-window{
display:flex;
flex-direction:column;
height:100%;
}
.chat-messages{
flex: 1;
height:100%;
overflow: auto;
display: flex;
flex-direction: column-reverse;
}
.chat-input { border-top: 1px solid #999; padding: 20px 5px }
.chat-input-text { width: 60%; min-height: 40px; max-width: 60%; }
The downside with flex-direction: column-reverse; is a bug in IE/Edge/Firefox, where the scrollbar doesn't show, which your can read more about here: Flexbox column-reverse and overflow in Firefox/IE
The upside is you have ~ 90% browser support on mobile/tablets and ~ 65% for desktop, and counting as the bug gets fixed, ...and there is a workaround.
// scroll to bottom
function updateScroll(el){
el.scrollTop = el.scrollHeight;
}
// only shift-up if at bottom
function scrollAtBottom(el){
return (el.scrollTop + 5 >= (el.scrollHeight - el.offsetHeight));
}
In the below code snippet I've added the 2 functions from above, to make IE/Edge/Firefox behave in the same way flex-direction: column-reverse; does.
function addContent () {
var msgdiv = document.getElementById('messages');
var msgtxt = document.getElementById('inputs');
var atbottom = scrollAtBottom(msgdiv);
if (msgtxt.value.length > 0) {
msgdiv.innerHTML += msgtxt.value + '<br/>';
msgtxt.value = "";
} else {
msgdiv.innerHTML += 'Long long content ' + (tempCounter++) + '!<br/>';
}
/* if at bottom and is IE/Edge/Firefox */
if (atbottom && (!isWebkit || isEdge)) {
updateScroll(msgdiv);
}
}
function resizeInput () {
var msgdiv = document.getElementById('messages');
var msgtxt = document.getElementById('inputs');
var atbottom = scrollAtBottom(msgdiv);
if (msgtxt.style.height == '120px') {
msgtxt.style.height = 'auto';
} else {
msgtxt.style.height = '120px';
}
/* if at bottom and is IE/Edge/Firefox */
if (atbottom && (!isWebkit || isEdge)) {
updateScroll(msgdiv);
}
}
/* fix for IE/Edge/Firefox */
var isWebkit = ('WebkitAppearance' in document.documentElement.style);
var isEdge = ('-ms-accelerator' in document.documentElement.style);
var tempCounter = 6;
function updateScroll(el){
el.scrollTop = el.scrollHeight;
}
function scrollAtBottom(el){
return (el.scrollTop + 5 >= (el.scrollHeight - el.offsetHeight));
}
html, body { height:100%; margin:0; padding:0; }
.chat-window{
display:flex;
flex-direction:column;
height:100%;
}
.chat-messages{
flex: 1;
height:100%;
overflow: auto;
display: flex;
flex-direction: column-reverse;
}
.chat-input { border-top: 1px solid #999; padding: 20px 5px }
.chat-input-text { width: 60%; min-height: 40px; max-width: 60%; }
/* temp. buttons for demo */
button { width: 12%; height: 44px; margin-left: 5%; vertical-align: top; }
/* begin - fix for hidden scrollbar in IE/Edge/Firefox */
.chat-messages-text{ overflow: auto; }
#media screen and (-webkit-min-device-pixel-ratio:0) {
.chat-messages-text{ overflow: visible; }
/* reset Edge as it identifies itself as webkit */
#supports (-ms-accelerator:true) { .chat-messages-text{ overflow: auto; } }
}
/* hide resize FF */
#-moz-document url-prefix() { .chat-input-text { resize: none } }
/* end - fix for hidden scrollbar in IE/Edge/Firefox */
<div class="chat-window">
<div class="chat-messages">
<div class="chat-messages-text" id="messages">
Long long content 1!<br/>
Long long content 2!<br/>
Long long content 3!<br/>
Long long content 4!<br/>
Long long content 5!<br/>
</div>
</div>
<div class="chat-input">
<textarea class="chat-input-text" placeholder="Type your message here..." id="inputs"></textarea>
<button onclick="addContent();">Add msg</button>
<button onclick="resizeInput();">Resize input</button>
</div>
</div>
Side note 1: The detection method is not fully tested, but it should work on newer browsers.
Side note 2: Attach a resize event handler for the chat-input might be more efficient then calling the updateScroll function.
Note: Credits to HaZardouS for reusing his html structure

You just need one CSS rule set:
.messages-container, .scroll {transform: scale(1,-1);}
That's it, you're done!
How it works: First, it vertically flips the container element so that the top becomes the bottom (giving us the desired scroll orientation), then it flips the content element so that the messages won't be upside down.
This approach works in all modern browsers. It does have a strange side effect, though: when you use a mouse wheel in the message box, the scroll direction is reversed. This can be fixed with a few lines of JavaScript, as shown below.
Here's a demo and a fiddle to play with:
//Reverse wheel direction
document.querySelector('.messages-container').addEventListener('wheel', function(e) {
if(e.deltaY) {
e.preventDefault();
e.currentTarget.scrollTop -= e.deltaY;
}
});
//The rest of the JS just handles the test buttons and is not part of the solution
send = function() {
var inp = document.querySelector('.text-input');
document.querySelector('.scroll').insertAdjacentHTML('beforeend', '<p>' + inp.value);
inp.value = '';
inp.focus();
}
resize = function() {
var inp = document.querySelector('.text-input');
inp.style.height = inp.style.height === '50%' ? null : '50%';
}
html,body {height: 100%;margin: 0;}
.conversation {
display: flex;
flex-direction: column;
height: 100%;
}
.messages-container {
flex-shrink: 10;
height: 100%;
overflow: auto;
}
.messages-container, .scroll {transform: scale(1,-1);}
.text-input {resize: vertical;}
<div class="conversation">
<div class="messages-container">
<div class="scroll">
<p>Message 1<p>Message 2<p>Message 3<p>Message 4<p>Message 5
<p>Message 6<p>Message 7<p>Message 8<p>Message 9<p>Message 10<p>Message 11<p>Message 12<p>Message 13<p>Message 14<p>Message 15<p>Message 16<p>Message 17<p>Message 18<p>Message 19<p>Message 20
</div>
</div>
<textarea class="text-input" autofocus>Your message</textarea>
<div>
<button id="send" onclick="send();">Send input</button>
<button id="resize" onclick="resize();">Resize input box</button>
</div>
</div>
Edit: thanks to #SomeoneSpecial for suggesting a simplification to the scroll code!

Please try the following fiddle - https://jsfiddle.net/Hazardous/bypxg25c/. Although the fiddle is currently using jQuery to grow/resize the text area, the crux is in the flex related styles used for the messages-container and input-container classes -
.messages-container{
order:1;
flex:0.9 1 auto;
overflow-y:auto;
display:flex;
flex-direction:row;
flex-wrap:nowrap;
justify-content:flex-start;
align-items:stretch;
align-content:stretch;
}
.input-container{
order:2;
flex:0.1 0 auto;
}
The flex-shrink value is set to 1 for .messages-container and 0 for .input-container. This ensures that messages-container shrinks when there is a reallocation of size.

I've moved text-input within messages, absolute positioned it to the bottom of the container and given messages enough bottom padding to space accordingly.
Run some code to add a class to conversation, which changes the height of text-input and bottom padding of messages using a nice CSS transition animation.
The JavaScript runs a "scrollTo" function at the same time as the CSS transition is running to keep the scroll at the bottom.
When the scroll comes off the bottom again, we remove the class from conversation
Hope this helps.
https://jsfiddle.net/cnvzLfso/5/
var doScollCheck = true;
var objConv = document.querySelector('.conversation');
var objMessages = document.querySelector('.messages');
var objInput = document.querySelector('.text-input');
function scrollTo(element, to, duration) {
if (duration <= 0) {
doScollCheck = true;
return;
}
var difference = to - element.scrollTop;
var perTick = difference / duration * 10;
setTimeout(function() {
element.scrollTop = element.scrollTop + perTick;
if (element.scrollTop === to) {
doScollCheck = true;
return;
}
scrollTo(element, to, duration - 10);
}, 10);
}
function resizeInput(atBottom) {
var className = 'bigger',
hasClass;
if (objConv.classList) {
hasClass = objConv.classList.contains(className);
} else {
hasClass = new RegExp('(^| )' + className + '( |$)', 'gi').test(objConv.className);
}
if (atBottom) {
if (!hasClass) {
doScollCheck = false;
if (objConv.classList) {
objConv.classList.add(className);
} else {
objConv.className += ' ' + className;
}
scrollTo(objMessages, (objMessages.scrollHeight - objMessages.offsetHeight) + 50, 500);
}
} else {
if (hasClass) {
if (objConv.classList) {
objConv.classList.remove(className);
} else {
objConv.className = objConv.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
}
}
}
}
objMessages.addEventListener('scroll', function() {
if (doScollCheck) {
var isBottom = ((this.scrollHeight - this.offsetHeight) === this.scrollTop);
resizeInput(isBottom);
}
});
html,
body {
height: 100%;
width: 100%;
background: white;
}
body {
margin: 0;
padding: 0;
}
.conversation {
display: flex;
flex-direction: column;
justify-content: space-between;
height: 100%;
position: relative;
}
.messages {
overflow-y: scroll;
padding: 10px 10px 60px 10px;
-webkit-transition: padding .5s;
-moz-transition: padding .5s;
transition: padding .5s;
}
.text-input {
padding: 10px;
-webkit-transition: height .5s;
-moz-transition: height .5s;
transition: height .5s;
position: absolute;
bottom: 0;
height: 50px;
background: white;
}
.conversation.bigger .messages {
padding-bottom: 110px;
}
.conversation.bigger .text-input {
height: 100px;
}
.text-input input {
height: 100%;
}
<div class="conversation">
<div class="messages">
<p>
This is a message content
</p>
<p>
This is a message content
</p>
<p>
This is a message content
</p>
<p>
This is a message content
</p>
<p>
This is a message content
</p>
<p>
This is a message content
</p>
<p>
This is a message content
</p>
<p>
This is a message content
</p>
<p>
This is a message content
</p>
<p>
This is a message content
</p>
<p>
This is a message content
</p>
<p>
This is a message content
</p>
<p>
This is a message content
</p>
<p>
This is a message content
</p>
<p>
This is the last message
</p>
<div class="text-input">
<input type="text" />
</div>
</div>
</div>

You write;
Now, consider this case:
The user scrolls to the bottom of the conversation
The .text-input, dynamically gets bigger
Wouldn't the method that dynamically sets the .text-input be the logical place to fire this.props.onResize().

To whom it may concern,
The answers above did not suffice my question.
The solution I found was to make my innerWidth and innerHeight variable constant - as the innerWidth of the browser changes on scroll to adapt for the scrollbar.
var innerWidth = window.innerWidth
var innerHeight = window.innerHeight
OR FOR REACT
this.setState({width: window.innerWidth, height: window.innerHeight})
In other words, to ignore it, you must make everything constant as if it were never scrolling. Do remember to update these on Resize / Orientation Change !

IMHO current answer is not a correct one:
1/ flex-direction: column-reverse; reverses the order of messages - I didn't want that.
2/ javascript there is also a bit hacky and obsolete
If you want to make it like a PRO use spacer-box which has properties:
flex-grow: 1;
flex-basis: 0;
and is located above messages. It pushes them down to the chat input.
When user is typing new messages and input height is growing the scrollbar moves up, but when the message is sent (input is cleared) scrollbar is back at bottom.
Check my snippet:
body {
background: #ccc;
}
.chat {
display: flex;
flex-direction: column;
width: 300px;
max-height: 300px;
max-width: 90%;
background: #fff;
}
.spacer-box {
flex-basis: 0;
flex-grow: 1;
}
.messages {
display: flex;
flex-direction: column;
overflow-y: auto;
flex-grow: 1;
padding: 24px 24px 4px;
}
.footer {
padding: 4px 24px 24px;
}
#chat-input {
width: 100%;
max-height: 100px;
overflow-y: auto;
border: 1px solid pink;
outline: none;
user-select: text;
white-space: pre-wrap;
overflow-wrap: break-word;
}
<div class="chat">
<div class="messages">
<div class="spacer-box"></div>
<div class="message">1</div>
<div class="message">2</div>
<div class="message">3</div>
<div class="message">4</div>
<div class="message">5</div>
<div class="message">6</div>
<div class="message">7</div>
<div class="message">8</div>
<div class="message">9</div>
<div class="message">10</div>
<div class="message">11</div>
<div class="message">12</div>
<div class="message">13</div>
<div class="message">14</div>
<div class="message">15</div>
<div class="message">16</div>
<div class="message">17</div>
<div class="message">18</div>
</div>
<div class="footer">
<div contenteditable role="textbox" id="chat-input"></div>
</div>
<div>
Hope I could help :)
Cheers

Related

Overflow-y: auto without changing the content padding when the scrollbar appears

I am trying to create a content container that doesn't change the content position when overflow causes a scrollbar to appear. I can do this easily when my containers are fixed widths but I am struggling to think of a way where I can use percentages to scale to the browser width. I am using an extra container with a negative right margin so the scroll bar appears in the padding of the parent container.
How can I make the content div stay the same width as the header div? I either need to be able to create a variable with the width of the header and use it for the width of content or have some sort of css that targets a container that has overflow-y enabled.
const chars = 'abcdefghijklmnopqrstuzwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
const contentContainer = document.getElementsByClassName('content')[0];
function addContent(num) {
for (let i = 0; i < num; i++) {
contentContainer.innerHTML += chars[Math.floor(chars.length * Math.random())] + ' ';
}
}
addContent(150);
document.getElementById('add').addEventListener('click', function() {
addContent(1500);
});
.container {
height: 100px;
border: 1px solid black;
padding: 20px;
}
.header {
background-color: #eee;
padding: 10px;
}
.scrollbar {
margin-right: -20px;
height: 100%;
overflow-y: auto;
}
.content {
padding-right: 20px;
}
<div class="container">
<div class="header"></div>
<div class="scrollbar">
<div class="content"></div>
</div>
</div>
<button id="add">Add scrollbar</button>

Scale font to size when text exceeds container?

I want font size to be 54px if the text fits inside the container, otherwise it should be 36px.
I was considering whether I can achieve this with a pure CSS solution, using the scale function to collapse to either of the two. If the container can be assumed to be full with, I guess I could use vw as a base for a calculation?
But I am very much stuck on this. Could anyone give me a hint, as to how I can achieve this or something close to it.
If you would like to put some text inside a container and have it size itself to fill that container, then CSS Tricks has an article on Fitting Text to a Container that will cover your options.
You could also use Viewport Sized Typography which take advantage of viewport units such as:
1vw = 1% of viewport width
1vh = 1% of viewport height
1vmin = 1vw or 1vh, whichever is smaller
1vmax = 1vw or 1vh, whichever is larger
One unit of any v* is 1% of the viewport axis. Where the “Viewport” == browser window size == window object. If the viewport is 50cm wide, 1vw == 0.5cm. Using viewport units alone such as font-size: 4vw; can make the text appear too big or too small when varying the window width and bring accessibility issues (as the user preferences are not taken into account).
Lastly, you could use clamp() to achieve Simplified Fluid Typography. Clamp takes three values, a min, max, and a flexible unit in the middle that it will use in case the value is between the min and max.
If you want the font size to be a minimum of 36px and maximum 54px, you could use clamp() like this and vary the "flexible unit" to your liking. Here is an example of fluid typography for an <h1> element inside a container.
body {
font-size: 1rem;
margin: 0 auto;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.container {
border: .2rem solid #f06;
padding: .5rem;
height: 100%;
width: auto;
}
.container h1 {
font-size: 36px; /* fallback */
font-size: clamp(36px, 10vw, 54px);
}
<body>
<div class="container">
<h1>Heading text</h1>
</div>
</body>
Browser support for clamp() is pretty good, but you’d probably want to put a font-size declaration before it to set an acceptable fallback value.
In conclusion, if you needed to set an explicit width and height for said container, you might want to use media queries along with viewport units, calc(), or clamp() depending on the size of the content box in which the text resides.
i think it's nearly impossible to calculate it without js.
below is an code example how i would do it in jquery or you could use the following jquery plugin, but i never tested this plugin before: FitText.js
for (let container of $('.text-container')){
container = $(container);
let textInner = $(container).find('.text-inner');
console.log(container.width());
console.log(textInner.width());
if (container.width()<textInner.width()){
textInner.addClass('fs-36');
textInner.removeClass('fs-54');
} else {
textInner.addClass('fs-54');
textInner.removeClass('fs-36');
}
}
.text-container,
.text-container-before{
position: relative;
width: 250px;
height: 60px;
background: #333;
color: #090;
overflow: visible;
margin: 20px 0;
}
.text-inner,
.text-before{
position: absolute;
top: 0;
left: 0;
height: 100%;
background: #0399;
word-break: keep-all;
white-space: nowrap;
}
.fs-54{
font-size: 54px;
}
.fs-36{
font-size: 36px;
}
/* just added because console window is hiding running code snipped */
.spacer{
height: 80px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h1>before</h1>
<div class="text-container-before">
<div class="text-before fs-54">a bit longer text</div>
</div>
<div class="text-container-before">
<div class="text-before fs-54">shorter text</div>
</div>
<h1>after</h1>
<div class="text-container">
<div class="text-inner fs-54">a bit longer text</div>
</div>
<div class="text-container">
<div class="text-inner fs-54">shorter text</div>
</div>
<div class="spacer"></div>
This question awsers was not exactly what I was looking for, so here is a solution to scale down the text if it would exceed the size of the parent container, by making it smaller until it fits the container.
for(const element of document.getElementsByClassName("shrink"))
{
var size = parseInt(getComputedStyle(element).getPropertyValue('font-size'));
const parent_width = parseInt(getComputedStyle(element.parentElement).getPropertyValue('width'))
while(element.offsetWidth > parent_width)
{
element.style.fontSize = size + "px"
size -= 1
}
}
.container{
border:1px dashed #ccc;
display:flex;
align-items:center;
justify-content:center;
width:200px;
height:50px;
margin:10px;
}
.shrink{
white-space: nowrap;
font-size:80px;
}
<div class="container"><span class="shrink">long text to shrink</span></div>
<div class="container"><span class="shrink">small text</span></div>
try word-break: break-all;, I think this will solve your problem
Not, Sure about CSS solution. But, My JS solution can resolve your problem.
Refer below code. Class class-54 and class-36 will change based on content:
// creating node
function createNode(element) {
return document.createElement(element);
}
// creating append
function append(parent, el) {
return parent.appendChild(el);
}
var getPageTitle = document.getElementById("pageTitle"),
textLengthWidth = 0;
const getPageTitleText = getPageTitle.textContent;
getPageTitle.innerHTML = "";
for (let index = 0; index < getPageTitleText.length; index++) {
const span = createNode("span");
span.innerHTML = getPageTitleText.charAt(index);
append(getPageTitle, span);
if (index == getPageTitleText.length - 1) {
calcFontSize();
}
}
function calcFontSize() {
var listOfSpan = document.querySelectorAll("#pageTitle span");
listOfSpan.forEach(element => {
textLengthWidth += element.offsetWidth;
});
console.log("total DIV width: " + getPageTitle.offsetWidth);
console.log("total Content width: " + textLengthWidth);
getPageTitle.offsetWidth < textLengthWidth ? getPageTitle.classList.add("class-36") : getPageTitle.classList.add("class-54");
}
* {
padding: 0;
margin: 0;
font-size: 14px;
}
.title {
display: block;
width: 100vw;
}
h1, h1 span {
font-size: 54px;
}
.class-54 span {
font-size: 54px;
}
.class-36 span {
font-size: 36px;
}
<div class="title">
<h1 id="pageTitle">Hello World! Hello World!</h1>
</div>
Try this
.thingy {
font-size:54px;
overflow-wrap: break-word;
}
<h1 class="thingy">Hel000000000000000000ooo</h1>

Smaller horizontal scrollbar than div width? + No figcaption?

I'm trying to do image "carousel" with horizontal scroll. Pure HTML + CSS without JS.
This is my HTML:
<div class="slideshow">
<figure class="slideshow__fig">
<img src="img/hilti-png.png" alt="" class="slideshow__fig-img">
<figcaption>Fig.1 - Trulli, Puglia, Italy.</figcaption>
</figure>
<figure class="slideshow__fig">
<img src="img/hilti-png.png" alt="" class="slideshow__fig-img">
<figcaption>Fig.1 - Trulli, Puglia, Italy.</figcaption>
</figure>
<figure class="slideshow__fig">
<img src="img/hilti-png.png" alt="p" class="slideshow__fig-img">
<figcaption>Fig.1 - Trulli, Puglia, Italy.</figcaption>
</figure>
</div>
This is my css:
.slideshow {
display: flex;
flex-wrap: nowrap;
overflow-y: hidden;
overflow-x: scroll;
//width: 80vw;
//margin: auto;
&::-webkit-scrollbar {
width: 350px;
height: 10px;
}
/* Track */
&::-webkit-scrollbar-track {
background: #f1f1f1;
}
/* Handle */
&::-webkit-scrollbar-thumb {
background: #888;
border-radius: 500px;
}
/* Handle on hover */
&::-webkit-scrollbar-thumb:hover {
background: #555;
}
&__fig {
flex: 0 0 auto;
height: 900px;
&-img{
height: 100%;
//width: 100%;
display: block;
}
}
}
1 - The problem is when I set the width of the .slideshow to 80vw, then naturally the scrollbar is shorter, but my images are cropped and not going full display width. When I try to adjust the width of the scrollbar with ::-webkit-scrollbar {width: 350px or 50vw or ...} exactly nothing happens.
I would like to have a scrollbar which is not full width of the div which I'm scrolling, but somehow can't figure it out.
2 - The other problem is I would like to have a figcaption at the bottom left side of the image. But somehow it doesn't show when the horizontal scroll is there. Any suggestions?
Here is the example how I would like to have it:
example image
edit: Now I finally managed to do it by adding:
&::-webkit-scrollbar-button:end:increment {
width: 50%;
display: block;
background: transparent;
}
But now the problem is that the scrollbar is not in middle, but on the left side. Margin:auto doesn't help. No idea how else to do it.
Also making img size 90% revealed the caption which is not that bad solution.
Now the only question is how to put the scroll bar in the centre.
Here is something close to the image you provided as an example. Sorry, but I really don't know how this can be achieved respecting the Pure HTML + CSS without JS criteria. I think it isn't possible at all.
So here, it uses jQuery and jQuery-ui draggable.
It uses a draggable div contained within its parent. Ondrag, it calculates the "scrolled" percentage to apply it to the scrollable width of the image slider.
For mobiles... I added the "touch punch" patch for jQuery-ui. More details about it here. I also placed the "initialisation code" in a function, so it can run on load AND on resize.
$(document).ready(function(){
function initDisplay(){
let slide_scrollWidth = $("#slide")[0].scrollWidth;
let customScrollbar_width = $("#sliderScroll_outer")[0].scrollWidth;
let percent = slide_scrollWidth/customScrollbar_width
$("#sliderScroll").css({"width":percent+"%", "left":0})
$("#slide")[0].scrollTo(0,0)
}
// On page load
initDisplay()
// Useful for mobile orientation change
window.onresize = initDisplay
$("#sliderScroll").draggable({
containment: "#sliderScroll_outer",
scroll: false,
drag: function(e){
let parentOffset = $(e.target).parent().offset().left
let offset = $(e.target).offset().left
let scrollableWidth = $(e.target).parent().width() - $(e.target).width()
let sliderPercent = (offset-parentOffset)/scrollableWidth
//console.log(sliderPercent)
let imageSliderWidth = $("#slide")[0].scrollWidth - $("#slide").width()
//console.log(imageSliderWidth)
$("#slide")[0].scrollTo(sliderPercent*imageSliderWidth,0)
}
});
});
#container{
margin: 1em;
}
#slide{
display: flex;
overflow: hidden;
}
#slide img{
margin: 0 0.5em;
}
#sliderScroll_outer{
width: 40vw;
background: lightgrey;
margin: 1em;
}
#sliderScroll{
width: 0vw;
height: 10px;
background: green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui-touch-punch/0.2.2/jquery.ui.touch-punch.min.js"></script>
<div id="container">
<div id="slide">
<img src="https://via.placeholder.com/800x600.png">
<img src="https://via.placeholder.com/400x600.png">
<img src="https://via.placeholder.com/1000x600.png">
</div>
<div id="sliderScroll_outer">
<div id="sliderScroll"></div>
</div>
</div>
Run in full page mode or CodePen

Can CSS Grid layout be used to implement resizing of editor panes (e.g. as in CodePen)

I'd like to implement resizable panes without JS, only using CSS Grid layout. Is this possible?
For e.g. CodePen's editor has resizable panes for its HTML / CSS / JS editors. Also, see the example below which implements it in plain JS (I can't seem to add a URL to the CodePen example in it, so it's a bit hard to add attribution. The code is by http://codepen.io/gsound/).
let isResizing = false;
let $handler = document.getElementById('handler');
let $wrapper = document.getElementById('wrapper');
let $left = document.getElementById('left');
$handler.addEventListener('mousedown', function(e){
isResizing = true;
});
document.addEventListener('mousemove', function(e){
if( !isResizing ) return;
let newWidth = e.clientX - $wrapper.offsetLeft;
$left.style.width = newWidth + 'px';
});
document.addEventListener('mouseup', function(){
isResizing = false;
});
html,body{
height: 100%;
width: 100%;
margin: 0;
}
#app{
height: 100%;
/* max-width: 1400px; */
margin: 0 auto;
}
header{
background-color: #AAA;
height: 50px;
}
#wrapper{
margin: 0 auto;
height: calc(100% - 50px);
width: 100%;
background-color: #EEE;
display: flex;
}
#handler{
background-color: red;
width: 5px;
cursor: col-resize;
}
#left{
width: 200px;
}
#content{
width: 100%;
flex: 1;
}
/* ----------- */
#left{
background-color: yellow;
}
#content{
background-color: #232378;
}
<div id="app">
<header>head</header>
<div id="wrapper">
<aside id="left"></aside>
<div id="handler"></div>
<article id="content"></article>
</div>
</div>
P.S as a side note, I have re-implemented the e.g. above by adding and removing 'mousemove', 'mouseup' events and was wondering whether that's "better" (more performant) than using a boolean isResizing and keeping the event listeners always there...
The Grid Layout module is pure CSS.
Resizable panes generally require JavaScript to function.
Knowing that CSS is designed primarily for styling and presentation, there are most likely no built-in properties or methods in the Grid Layout spec that would provide for manually resizable panes.

Any reliable, cross-browser way to distribute the remaining space in parent element between several DIVs?

Background
I am working on a browser-based UI that needs to fill the entire screen without any scrolling. The basic layout is like this:
What I want to achieve
The title div should has a fixed height (2em) and the rest 4 divs/panels should devide the remaining space of the screen according to percentages I set.
What I've tried
The best solution I've found is " CSS - How to force elements to 100% of remaining/available space of parent element without extending beyond it? ", which involves using a container div with position:absolute. This works across all browsers, but requires some additional DIVs to be created. Also, panel 2 can sometimes be forced to start on the next line due to inaccuracies in percentage widths.
My previous solution was based on CSS3 Flexbox, but the model is flawed as it does not resize child elements that have a percentage height after stretching the container boxes (at least Chrome doesn't). (The newer flex-* attributes are only implemented in Chrome and the standard is still changing.)
I have also tried the calc() function; however, it's not yet implemented in Chrome. Also, it requires hard-coding the height of the title element in two places, which I've been trying to avoid.
Edit:
What am I looking for
Just to be clear, I am not asking for a perfect/pure-CSS solution (as none seems to exist). If anyone can suggest any jQuery plug-in or open-source framework that can do this, it would be good enough for me.
In addition, I don't require any backwards compatibility with browser releases before 2012. (As long as the solution uses technology that is implemented in some browser and is going to be implemented by Firefox and Chrome in the near future, it's good enough for me.)
A little something thrown together:
http://jsfiddle.net/gDTGn/2/
Here is a pure CSS version:
http://jsfiddle.net/t0nyh0/KHzsg/63/
The trick to this technique is using position:absolute and using top, bottom, and height to create a fixed header with expanding panels. It is also really important to use:
box-sizing: border-box;
-moz-box-sizing: border-box;
to make the height and width calculations consistent across browsers. Tested and works in IE9, Firefox, and Chrome.
Pure CSS solution: http://jsfiddle.net/ehqcx/7/embedded/result/
This assumes you set width that don't sum up than more than 100%, the small gap at the right side can usually be fixed by using the same background or the background of the page. An alternative is to introduce some Javascript that sets the width of the last panel correctly, but that should be some trivial jQuery code... $("#panels .small:last").width(browser width - other small panels);
Should work correctly for the height, think away the jsFiddle header which takes away some height...
Edit:
Meh, seems the #title is bugging me... http://fiddle.jshell.net/ehqcx/7/show/light/
ECMAScript is the way to go, leaving my answer in place because of the other simplicity... :(
HTML:
<div id="content">
<div id="title">Title!</div>
<div id="panels">
<div id="panel0" class="small">0</div>
<div id="panel1" class="small">1</div>
<div id="panel2" class="small">2</div>
<div id="panel3" class="wide">3</div>
</div>
</div>​
CSS:
* { margin, padding: 0px; }
#content { background-color: black; }
#title { background-color: red; }
#panels { background-color: orange; }
#panel0 { background-color: purple; }
#panel1 { background-color: brown; }
#panel2 { background-color: orange; }
#panel3 { background-color: green; }
html, body, #content, #panels { max-height: 100%; height: 100%; max-width: 100%; width: 100%; }
#panels .small { float: left; }
#panels .wide { clear: both; }
#title { height : 2em; }
#panels .small { height: 75%; }
#panels .wide { height: 25%; }
#panel0, #panel1, #panel2 { width: 33.33%; }
It's possible using the new CSS3 flexbox model. It was basically designed to solve the problem you are facing.
Here is a simple example:
CSS:
*{margin:0 padding:0;}
html{height:100%;}
body{height:100%; display:box; box-orient:vertical;}
body > div {box-flex:1; border:1px solid black;}
.header {box-flex:0; height:4em;}
.content {
display: box;
box-orient: horizontal;
}
.content div {
box-flex: 1;
border:1px solid black;
}
HTML:
<html>
<body>
<div class="header">Title</div>
<div class="content">
<div>Panel 0</div>
<div>Panel 1</div>
<div>Panel 2</div>
</div>
<div>Panel 3</div>
</body>
</html>
It has good support in Chrome, Safari, and Firefox, with planned support in IE.
edit 2:
Tested it in
Chrome/Safari: some 1 or two pixel failure, because of percent calculations
FireFox: Perfect
IE9: Perfect
Opera: Can't have decimal places in percentage width values. This is bad
lte IE8: Does not support Array reduce function. One has to make one up (like from here: Array.reduce), Then it works at least in IE8
edit 1:
I added horizontal layout and window resize function
I've fiddled around a bit:
This is just a demonstration: To have a full fledged application you have to add the programming for the horizontal layout. But it's start
http://jsfiddle.net/HerrSerker/PmHtf/
Here is the code
HTML
<div class="full-stretch">
<div class="flex-layout flex-layout-vertical">
<div class="flex-layout-fixed" style="height:50px; text-align: center">
<div class="padding">Title</div>
</div>
<div class="flex-layout-consume flex-layout-consume-3" style="text-align: center">
<div class="flex-layout flex-layout-horizontal">
<div class="flex-layout-consume flex-layout-consume-1" style="text-align: center">
<div class="padding">Panel 0</div>
</div>
<div class="flex-layout-consume flex-layout-consume-1" style="text-align: center">
<div class="padding">Panel 1</div>
</div>
<div class="flex-layout-consume flex-layout-consume-1" style="text-align: center">
<div class="padding">Panel 2</div>
</div>
</div>
</div>
<div class="flex-layout-consume flex-layout-consume-1" style="text-align: center">
<div class="padding">Panel 3</div>
</div>
</div>
</div>​
CSS
.full-stretch {
position: absolute;
top: 2px;
right:2px;
bottom:2px;
left: 2px;
}
.padding {
position: absolute;
top: 2px;
right:2px;
bottom:2px;
left: 2px;
border: 1px solid darkGray;
background: lightBlue;
border-radius: 10px;
}
.flex-layout {
position: absolute;
top: 0;
right:0;
bottom:0;
left: 0;
overflow: hidden;
}
.flex-layout-consume {
height: 100%;
float:left;
overflow: hidden;
position: relative;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.flex-layout-vertical > .flex-layout-consume {
width: 100%;
}
.flex-layout-fixed {
height: 100%;
float:left;
overflow: hidden;
position: relative;
}
.flex-layout-vertical > .flex-layout-fixed {
width: 100%;
}
jQuery
(function($) {
var flex = function() {
$('.flex-layout').each(function() {
var fixed = $(this).children('.flex-layout-fixed');
if ($(this).hasClass('flex-layout-horizontal')) { // horizontal
var fixed_widths = $(this)
.children('.flex-layout-fixed')
.get()
.reduce(function(total, elem) {
return (total + $(elem).outerWidth())
},0)
;
var remain_width = ($(this).outerWidth() - fixed_widths)/$(this).outerWidth() * 100; // percent
var consumers = $(this)
.children('.flex-layout-consume')
.get()
;
var count_consumers = consumers
.reduce(function(total, elem) {
var cm = parseInt($(elem).attr('class').match(/flex-layout-consume-(\d+)/)[1]);
$(elem).data('consume_multiplicator', cm);
return total + cm;
},0)
;
var consumers_tic = (remain_width/count_consumers)
$(consumers).each(function() {
$(this).width(Math.round((consumers_tic * $(this).data('consume_multiplicator'))*1000)/1000+'%')
})
} else if ($(this).hasClass('flex-layout-vertical')) { // vertical
var fixed_heights = $(this)
.children('.flex-layout-fixed')
.get()
.reduce(function(total, elem) {
return (total + $(elem).outerHeight())
},0)
;
var remain_height = ($(this).outerHeight() - fixed_heights)/$(this).outerHeight() * 100; // percent
var consumers = $(this)
.children('.flex-layout-consume')
.get()
;
var count_consumers = consumers
.reduce(function(total, elem) {
var cm = parseInt($(elem).attr('class').match(/flex-layout-consume-(\d+)/)[1]);
$(elem).data('consume_multiplicator', cm);
return total + cm;
},0)
;
var consumers_tic = (remain_height/count_consumers)
$(consumers).each(function() {
$(this).height(Math.round((consumers_tic * $(this).data('consume_multiplicator'))*1000)/1000+'%')
})
}
})
};
$(function() {
flex()
$(self).resize(flex)
})
}(jQuery))
​
I might be missing something in your question, but see if this is what you are looking for. Pure CSS solution that works in all browsers down to IE7.
http://jsfiddle.net/nyHgM/1/
This is my suggestion (pure css)... Tested on IE7+, Chrome & FF http://jsfiddle.net/victmo/hKGUe/
HTML
<div id='header'></div>
<div id='col0'></div>
<div id='col1'></div>
<div id='col2'></div>
<div id='footer'></div>
CSS
div{
position:absolute;
}
#header{
top:0px;
left:0px;
right:0px;
height:3em;
}
#footer{
bottom:0px;
left:0px;
right:0px;
height:2em;
}
#col0,
#col1,
#col2{
top:3em; /* header height */
bottom:2em; /* footer height */
width:33.33%;
}
#col0{ left:0%; width:30%; } /* left = 0 */
#col1{ left:30%; width:40%; } /* left = 0 + 30 */
#col2{ left:70%; width:30%; } /* left = 30 + 40 */
/* Colors */
#header{ background:#bbb; }
#col0{ background:#ccc; }
#col1{ background:#ddd; }
#col2{ background:#eee; }
#footer{ background:#aaa; }
​

Resources