CSS Multi Line ellipsis Cross Browser - css

There is a "div" in my webpage that has fixed width and height.
Following css only works with single line text:
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
How can I apply ellipsis on multiline text on the text inside that div using pure css with cross browser compatibility?

Try this example:
display: block; /* Fallback for non-webkit */
display: -webkit-box;
max-width: 400px;
height: $font-size*$line-height*$lines-to-show; /* Fallback for non-webkit */
margin: 0 auto;
font-size: $font-size;
line-height: $line-height;
-webkit-line-clamp: $lines-to-show;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
http://codepen.io/martinwolf/pen/qlFdp
or go for dotdotdot.js

This workaround will require a wrapping element and has a small caveat of covering the very end of your text if it exactly fills your content box, but it works well enough in fluid situations until a better css property (like line-clamp) is widely implemented.
Works best with text-align:justified, but not necessary.
https://codepen.io/freer4/pen/prKLPy
html, body, p { margin: 0; padding: 0; font-family: sans-serif;line-height:22px;}
.ellipsis{
overflow:hidden;
margin-bottom:1em;
position:relative;
}
.ellipsis:before {
content: "\02026";
position: absolute;
bottom: 0;
right:0;
width: 3em;
height:22px;
margin-left: -3em;
padding-right: 5px;
text-align: right;
background-size: 100% 100%;
background: linear-gradient(to right, rgba(255, 255, 255, 0), white 50%, white);
z-index:2;
}
.ellipsis::after{
content:"";
position:relative;
display:block;
float:right;
background:#FFF;
width:3em;
height:22px;
margin-top:-22px;
z-index:3;
}
/*For testing*/
.ellipsis{
max-width:500px;
text-align:justified;
}
.ellipsis-3{
max-height:66px;
}
.ellipsis-5{
max-height:110px;
}
<div class="ellipsis ellipsis-3">
<p>Reacts to content height. That is, you don't need to fix the height of your content containers. We expect no ellipsis here (unless your viewport is narrow)</p>
</div>
<div class="ellipsis ellipsis-3">
<p>Here we can have a great many lines of text and it works as we expect it to. Here we can have a great many lines of text and it works as we expect it to. Here we can have a great many lines of text and it works as we expect it to. Here we can have a great many lines of text and it works as we expect it to.</p>
</div>
<div class="ellipsis ellipsis-5">
<p>The number of lines shown is easily controlled by setting the max-height of the .ellipsis element. The downsides are the requirement of a wrapping element, and that if the text is precisely as long as your number of lines, you'll get a white area covering the very trailing end of your text. You've been warned. This is just some pushing text to make the element longer. See the ellipsis? Yay.</p>
</div>

To bad CSS doesn't support cross-browser multiline clamping, only WebKit seems to be pushing it. Any other hacky solutions don't really seem worth it at the moment because even they have their own issues.
I know how you want pure CSS and probably have your own Javascript alternative options but you could try and use a simple Javascript ellipsis library like Ellipsity on github the source code is very clean and small so if you do need to make any additional changes it should be quite easy.
https://github.com/Xela101/Ellipsity
I'm really wanting a pure CSS solution to this too to speed things up and make everything look more pretty without the need of external dependencies.

var explorer = detectIE();
function detectIE() {
var ua = window.navigator.userAgent;
var msie = ua.indexOf('MSIE ');
if (msie > 0) {
// IE 10 or older => return version number
return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
}
var trident = ua.indexOf('Trident/');
if (trident > 0) {
// IE 11 => return version number
var rv = ua.indexOf('rv:');
return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
}
var edge = ua.indexOf('Edge/');
if (edge > 0) {
// Edge (IE 12+) => return version number
return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);
}
// other browser
return false;
}
var firefox = navigator.userAgent.indexOf('Firefox') > -1;
if ((explorer) || (firefox)) {
var fontSize = $(".ellipsis-2").css('font-size');
var fontSize = parseInt(fontSize, 10);
var lineHeight = fontSize + 4;
var height = lineHeight * 2;
$(".ellipsis-2").css("line-height", lineHeight + "px");
$(".ellipsis-2").css("height", height);
$(".ellipsis-2").css({
"overflow": "hidden",
"position": "relative",
"word-break": "break-all"
});
var divheight = $(".ellipsis-2").height();
var lineheight = $(".ellipsis-2").css('line-height').replace("px", "");
var countline = Math.round(divheight / parseInt(lineheight));
// if you want to show 2 line
if (countline > 2) {
$(".ellipsis-2").addClass("dotted");
};
}
.ellipsis-2 {
-webkit-hyphens: auto;
-moz-hyphens: auto;
-ms-hyphens: auto;
hyphens: auto;
position: relative;
}
.dotted:after {
content: "...";
position: absolute;
bottom: 0;
right: 0;
background: #fff;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p class="ellipsis-2">Reacts to content height. That is, you don't need to fix the height of your content containers. We expect no ellipsis here (unless your viewport is narrow)</p>

You could solve it using some after pseudo classes. As text-overflow: ellipsis doesn't render the same cross browser we are adding ellipsis using the content attribute that you can provide to the :after class. When we are setting white-space: nowrap to the p we need to add the div:after "hack" to ensure that the text is clipped where the padding sets in.
HTML:
<div>
<p>This is a text that clips to ellipsis because it is long</p>
<p>This is a text that clips to ellipsis because it is long</p>
</div>
CSS
div {
width: 200px;
padding: 20px;
border: 1px solid #ccc;
overflow: hidden;
position: relative;
}
//Solves the overflow issue of the white-space: nowrap
div:after {
content: '';
position: absolute;
top: 0;
right: 0;
bottom: 0;
width: 20px;
background: #fff;
z-index: 1;
}
p {
white-space: nowrap;
display: inline-block;
}
p:after {
content: '...';
position: absolute;
right: 5px;
z-index: 2;
}
JSFiddle
Edit
I can see that I might have misread your question a bit. My code will fix Cross-browser ellipsis rendering but not for multi-line text. Check out this post for more answers on your specific topic: Limit text length to n lines using CSS: Stack Overflow

Related

How can I combine the same CSS properties for different elements?

I have two elements: tooltip and tooltip-line.
There is common properties for each elements:
[tooltip]::after, [tooltip-line]::after {
position: absolute;
opacity: 0;
visibility: hidden;
/* Other common properties */
}
Next, I have different properties for each element.
[tooltip-line]::after { /* One line tooltip */
content: attr(tooltip-line);
white-space: nowrap;
}
[tooltip]::after { /* Multiline tooltip */
content: attr(tooltip);
width: 200px;
white-space: normal;
}
Is this a correct usage? Including similar classes. Or should I copy all properties to each declaration block?
Here's a different approach which might be slightly more scalable. Using CSS custom variables, we can override any default class values by resetting them in the multiline class. Finally, I would make the attributes containing the tooltip content identical—and valid data attributes—if possible.
.tooltip::after {
--tooltip-white-space: nowrap;
content: attr(data-tooltip-content);
white-space: var(--tooltip-white-space);
}
.tooltip.multiline::after {
--tooltip-white-space: normal;
}
.container {
width: 250px;
border: 1px solid red;
}
<div class="container">
<div class="tooltip" data-tooltip-content="my tooltip content should not wrap no matter what"></div>
<div class="tooltip multiline" data-tooltip-content="my multliline tooltip content should wrap"></div>
</div>
jsFiddle
It's absolutely right to divide the css in multiple blocks.
One of the first thing to know while writing code in any language is NOT to repeat yourself.

Change the size of the number input spinner?

On a number input it has a spinner which has several css properties but I can't seem to find a way to change the size of the spinner itself. I am talking about <input type='number'>. I tried finding something that would change the size but I haven't been able to find anything. The other issue I guess is that every browser on possibly every OS is going to have a potentially different implementation of the spinner itself. When I say spinner I am talking about the highlighted part of this image.
I cannot use the JQuery UI spinner because the large app I am developing uses JQuery UI 1.8 which did not include the spinner. Upgrading causes issues.
Not ideal, but try playing around with the CSS transform property:
For example,
input[type=number]
{
transform: scale(2);
}
This increases the size of the entire input, but maybe this (in conjunction with setting font-size, line-height, height, width) can produce a desired effect.
This CSS seems to work in Chrome by replacing the spinners with a static image (of a spinner) and then control the size and position of the image within the element and making it invisible by default until the user hovers over it:
* Spin Buttons modified */
input[type="number"].mod::-webkit-outer-spin-button,
input[type="number"].mod::-webkit-inner-spin-button {
-webkit-appearance: none;
background: #0F0 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAKUlEQVQYlWNgwAT/sYhhKPiPT+F/LJgEsHv37v+EMGkmkuImoh2NoQAANlcun/q4OoYAAAAASUVORK5CYII=) no-repeat center center;
width: 3em;
border-left: 1px solid #0f0;
opacity: 0; /* shows Spin Buttons per default (Chrome >= 39) */
position: absolute;
top: 0;
right: 0;
bottom: 0;
}
input[type="number"].mod::-webkit-inner-spin-button:hover,
input[type="number"].mod::-webkit-inner-spin-button:active{
box-shadow: 0 0 2px #0CF;
opacity: .7;
}
Plain ole HTML...
No library or images required.
HTML
<!-- Score Control Container -->
<div class = "Score-Control">
<div class = "Score-Value-Container">
<div id="RoundScore" class="Score-Value">
10
</div>
</div>
<div class = "Score-UpDown">
<div class = "Score-Button-Container">
<div class = "Score-Button " onclick="IncrementScore();">
▲
</div>
</div>
<div class = "Score-Button-Container">
<div class = "Score-Button " onclick="DecrementScore();">
▼
</div>
</div>
</div>
</div>
CSS
.Score-Control {
width: 200px;
}
.Score-Value-Container{
position:relative;
display: table;
overflow: hidden;
height:80px;
background-color:#aaa;
width:66%;
float:left;
font-size: 44px;
vertical-align: middle;
text-align: center;
}
.Score-Value {
display: table-cell;
vertical-align: middle;
text-align: center;
}
.Score-UpDown{
position:relative;
height:80px;
background-color: burlywood;
width:34%;
float:right;
}
.Score-Button-Container {
display: table;
height: 50%;
width: 100%;
overflow: hidden;
background-color:green;
}
.Score-Button {
display: table-cell;
vertical-align: middle;
text-align: center;
font-size: 27px;
}
JavaScript
function IncrementScore() {
var RoundScore = document.getElementById("RoundScore").innerHTML;
if (RoundScore < 10) {
RoundScore++
document.getElementById("RoundScore").innerHTML = RoundScore;
}
}
function DecrementScore() {
var RoundScore = document.getElementById("RoundScore").innerHTML;
if (RoundScore > 1) {
RoundScore--
document.getElementById("RoundScore").innerHTML = RoundScore;
}
}
Code in JSFiddle
You could make an input field with two buttons for up and down and style them then the way you like.
<input type="text" name="something">
<span class="goUp"></span>
<span class="goDown"></span>
js:
var inputField = $('input[name="something"]');
$('.goUp').click(function() {
inputField.val(inputField.val() + 1);
});
$('.goDown').click(function() {
inputField.val(inputField.val() - 1);
});
you then should also check, that the input has only numbers inside, so that your +/- 1 really works.
The “size of the spinner” is a vague concept, but the <input type=number> element seems to obey at least width, height, and font property settings. Example:
<input type=number value=42 min=0 max=99
style="font: 24pt Courier; width: 3ch; height: 3em">
Whether such settings are useful and whether they should work is a different issue. It can be argued that the implementation of such elements is expected to be a browser-dependent nice, useable widget suitable for the browsing conditions, rather than something that authors should mess around with. But in practice, the widget is largely affected by CSS settings, and this might be a good thing in practice, e.g. because the input box tends to be too wide by default. (We could expect browsers to set it according to min and max values, but this just doesn’t happen at present.) The risk is that by setting the width, you might conflict with the implementation. The code above expects the up and down arrows to take a width of one character at most, but this guess might some day be wrong.

Customize colorbox caption (long caption and position of it)

Please see this link for example.
How can add a long caption under popup? For example, adding 3 lines like this:
we can add one line to title of tag and I tried to change this style
#cboxTitle {
position: absolute;
bottom: -25px;
left: 0px;
text-align: center;
width: 100%;
font-weight: bold;
color: #7C7C7C;
}
by adding height or top:800px, caption deisapear
This solution seems to work for me:
$(document).bind('cbox_complete', function(){
var cboxTitleHeight = $('#cboxTitle').height();
var cboxContentHeight = $('#cboxContent').height();
var cboxWrapperHeight = $('#cboxWrapper').height();
var colorboxHeight = $('#colorbox').height();
$('#cboxMiddleLeft, #cboxMiddleRight, #cboxContent').css('height', (cboxContentHeight + cboxTitleHeight) + 'px');
$('#cboxWrapper').css('height', (cboxWrapperHeight + cboxTitleHeight) + 'px');
$('#colorbox').css('height', (colorboxHeight + cboxTitleHeight) + 'px');
});
I also found this solution https://gist.github.com/eddyyanto/1620742 , but doesn't seem to work with v.1.5.x of colorbox.
Have you tried using this?
#cboxTitle {
position: absolute;
bottom: -25px;
left: 0;
text-align: right; /* MODIFIED */
width: 600px; /* MODIFIED */
font-weight: bold;
color: #7C7C7C;
word-break: break-word; /* ADDED */
margin-right: 100px; /* ADDED */
margin-left: 100px; /* ADDED */
}
This will allow for 2 lines of text!
Then to allow for more room remove for another line remove the following piece of CSS from the above. ( Use "/* CSS HERE */" to comment out so you can keep the code!) Example below
/* bottom: -25px; */
After that it will take a little fiddling around to get the "image 3 of 3" part moved :)
Hope this helps

HTML/CSS subtleties: How do I place "pins" on a resizeable "map"?

Don't be intimidated by the length of this post! (I've just copied the code from jsfiddle)
I'm trying to fix "pins" on an image ("map") that fits all screen-sizes without becoming distorted (i.e. max-height and max-width are 100%) and I've managed to get the pins somewhat fixed. However, although the following code(in the jsfiddle) looks like it works for some sizes, the pins slowly move away from their place as the dimensions of the window change. Have a look: (if you don't see the change in the fiddle itself, you can try copying and pasting the code onto a text-editor and then opening it on a browser. Resize the browser to a long-narrow-strip or a wide-narrow-strip and you'll see what I mean):
http://jsfiddle.net/bKbWf/
Thanks to anyone who can help solve this.
The code (same as the stuff on the above jsfiddle) is posted below:
(HTML):
<div class="mapNpins"> <!--map and pins begin here-->
<div class="map-container"><img class ="map" src="http://www.placekitten.com/1024/635"/>
<ul class = "ulist">
<li class="pin1"><img src="http://www.placekitten.com/20/20"></li>
<li class="pin2"><img src="http://www.placekitten.com/20/20"></li>
</ul>
</div> <!-- map and pins end here -->
</div>
(CSS):
.mapNpins { /*can be used to manipulate/position the entire mapNpins block*/
display: block;
border: 2px solid red;}
.map-container {
position: relative; /*this is so that later each list-element
(i.e. pin) can be positioned with respect to the map-picture
(i.e. the pins will be able to move/resize with the map)*/
text-align: center; /*centers map horizontally*/}
.map{
padding-left: 0.7%; /*misc. image correction*/
max-width: 100%; /* map image resizes with screen*/
max-height: 100%;/* map image resizes with screen*/}
.ulist {
list-style: none; /*to remove the bullets*/}
.ulist li a {
display: block;
position: absolute; /*allows each list-element to be controlled
individually, but all with respect to .map-container (which
is the first parent that has pos-relative)*/}
/*positioning the pins*/
.map-container .ulist .pin1 a {
text-align: center;
border: 2px solid orange; /*border color only for recognition*/
left: 25%; top:37%;}
.map-container .ulist .pin2 a {
border: 2px solid blue; /*border color only for recognition*/
left: 35%; top:47%;}
You could try a jquery for loop:
$(window).load(function(){
$(window).resize(function(){
for(i = 1; i <= 2000; i++) {
if($(window).width() === i) {
// set the css due to the ratio
$('.pin1').css('margin-left', ($('.pin1').css('margin-left') + i));
}
}
});
});

Prevent body scrolling but allow overlay scrolling

I've been searching for a "lightbox" type solution that allows this but haven't found one yet (please, suggest if you know of any).
The behavior I'm trying to recreate is just like what you'd see at Pinterest when clicking on an image. The overlay is scrollable (as in the whole overlay moves up like a page on top of a page) but the body behind the overlay is fixed.
I attempted to create this with just CSS (i.e. a div overlay on top of the whole page and body with overflow: hidden), but it doesn't prevent div from being scrollable.
How to keep the body/page from scrolling but keep scrolling inside the fullscreen container?
Theory
Looking at current implementation of the pinterest site (it might change in the future), when you open the overlay, a noscroll class is applied to the body element (setting overflow: hidden) making the body no longer scrollable.
The overlay created on-the-fly or already injected in the page and made visible via display: block — it makes no difference – has position : fixed and overflow-y: scroll, with top, left, right and bottom properties set to 0: this style makes the overlay fill the whole viewport (but now we are in 2022, so you may use inset: 0 instead).
The div inside the overlay is in position: static so the vertical scrollbar is related to that element. This is resulting in a scrollable but fixed overlay.
When you close the overlay, you have to hide it (using display: none) and you could even remove the node via javascript (or just the content inside, it's up to you but also depends on the nature of the content).
The final step is to also remove the noscroll class applied to the body (so the overflow property gets back to the value it had previously)
Code
Codepen Example
(it works by changing the aria-hidden attribute of the overlay in order to show and hide it and to increase its accessibility).
Markup
(open button)
<button type="button" class="open-overlay">OPEN LAYER</button>
(overlay and close button)
<section class="overlay" aria-hidden="true" tabindex="-1">
<div>
<h2>Hello, I'm the overlayer</h2>
...
<button type="button" class="close-overlay">CLOSE LAYER</button>
</div>
</section>
CSS
.noscroll {
overflow: hidden;
}
.overlay {
position: fixed;
overflow-y: scroll;
inset: 0; }
[aria-hidden="true"] { display: none; }
[aria-hidden="false"] { display: block; }
Javascript (vanilla-JS)
var body = document.body,
overlay = document.querySelector('.overlay'),
overlayBtts = document.querySelectorAll('button[class$="overlay"]'),
openingBtt;
[].forEach.call(overlayBtts, function(btt) {
btt.addEventListener('click', function() {
/* Detect the button class name */
var overlayOpen = this.className === 'open-overlay';
/* storing a reference to the opening button */
if (overlayOpen) {
openingBtt = this;
}
/* Toggle the aria-hidden state on the overlay and the
no-scroll class on the body */
overlay.setAttribute('aria-hidden', !overlayOpen);
body.classList.toggle('noscroll', overlayOpen);
/* On some mobile browser when the overlay was previously
opened and scrolled, if you open it again it doesn't
reset its scrollTop property */
overlay.scrollTop = 0;
/* forcing focus for Assistive technologies but note:
- if your modal has just a phrase and a button move the
focus on the button
- if your modal has a long text inside (e.g. a privacy
policy) move the focus on the first heading inside
the modal
- otherwise just focus the modal.
When you close the overlay restore the focus on the
button that opened the modal.
*/
if (overlayOpen) {
overlay.focus();
}
else {
openingBtt.focus();
openingBtt = null;
}
}, false);
});
/* detect Escape key when the overlay is open */
document.body.addEventListener('keyup', (ev) => {
if (ev.key === "Escape" && overlay.getAttribute('aria-hidden') === 'false') {
overlay.setAttribute('aria-hidden', 'true');
body.classList.toggle('noscroll', false);
openingBtt.focus();
openingBtt = null;
}
})
Finally, here's another example in which the overlay opens with a fade-in effect by a CSS transition applied to the opacity property. Also a padding-right is applied to avoid a reflow on the underlying text when the scrollbar disappears.
Codepen Example (fade)
CSS
.noscroll { overflow: hidden; }
#media (min-device-width: 1025px) {
/* not strictly necessary, just an experiment for
this specific example and couldn't be necessary
at all on some browser */
.noscroll {
padding-right: 15px;
}
}
.overlay {
position: fixed;
overflow-y: scroll;
inset: 0;
}
[aria-hidden="true"] {
transition: opacity 1s, z-index 0s 1s;
width: 100vw;
z-index: -1;
opacity: 0;
}
[aria-hidden="false"] {
transition: opacity 1s;
width: 100%;
z-index: 1;
opacity: 1;
}
overscroll-behavior css property allows to override the browser's default overflow scroll behavior when reaching the top/bottom of content.
Just add the following styles to overlay:
.overlay {
overscroll-behavior: contain;
...
}
Codepen demo
Currently works in Chrome, Firefox and IE(caniuse)
For more details check google developers article.
If you want to prevent overscrolling on ios, you can add position fixed to your .noscroll class
body.noscroll{
position:fixed;
overflow:hidden;
}
Most solutions have the problem that they do not retain the scroll position, so I took a look at how Facebook does it. In addition to setting the underlaying content to position: fixed they also set the top dynamically to retain the scroll position:
scrollPosition = window.pageYOffset;
mainEl.style.top = -scrollPosition + 'px';
Then, when you remove the overlay again, you need to reset the scroll position:
window.scrollTo(0, scrollPosition);
I created a little example to demonstrate this solution
let overlayShown = false;
let scrollPosition = 0;
document.querySelector('.toggle').addEventListener('click', function() {
if (!overlayShown) {
showOverlay();
} else {
removeOverlay();
}
overlayShown = !overlayShown;
});
function showOverlay() {
scrollPosition = window.pageYOffset;
const mainEl = document.querySelector('.main-content');
mainEl.style.top = -scrollPosition + 'px';
document.body.classList.add('show-overlay');
}
function removeOverlay() {
document.body.classList.remove('show-overlay');
window.scrollTo(0, scrollPosition);
const mainEl = document.querySelector('.main-content');
mainEl.style.top = 0;
}
.main-content {
background-image: repeating-linear-gradient( lime, blue 103px);
width: 100%;
height: 200vh;
}
.show-overlay .main-content {
position: fixed;
left: 0;
right: 0;
overflow-y: scroll; /* render disabled scroll bar to keep the same width */
/* Suggestion to put: overflow-y: hidden;
Disabled scrolling still makes a mess with its width. Hiding it does the trick. */
}
.overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.3);
overflow: auto;
}
.show-overlay .overlay {
display: block;
}
.overlay-content {
margin: 50px;
background-image: repeating-linear-gradient( grey, grey 20px, black 20px, black 40px);
height: 120vh;
}
.toggle {
position: fixed;
top: 5px;
left: 15px;
padding: 10px;
background: red;
}
/* reset CSS */
body {
margin: 0;
}
<main class="main-content"></main>
<div class="overlay">
<div class="overlay-content"></div>
</div>
<button class="toggle">Overlay</button>
Don't use overflow: hidden; on body. It automatically scrolls everything to the top. There's no need for JavaScript either. Make use of overflow: auto;. This solution even works with mobile Safari:
HTML Structure
<div class="overlay">
<div class="overlay-content"></div>
</div>
<div class="background-content">
lengthy content here
</div>
Styling
.overlay{
position: fixed;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
background-color: rgba(0, 0, 0, 0.8);
.overlay-content {
height: 100%;
overflow: scroll;
}
}
.background-content{
height: 100%;
overflow: auto;
}
See the demo here and source code here.
Update:
For people who want keyboard space bar, page up/down to work: you need to focus on the overlay, e.g., clicking on it, or manually JS focusing on it before this part of the div will respond to keyboard. Same with when the overlay is "switched off", since it's just moving the overlay to the side. Otherwise to browser, these are just two normal divs and it wouldn't know why it should focus on any one of them.
It is worth noting that sometimes adding "overflow:hidden" to the body tag doesn't do the job. In those cases, you'll have to add the property to the html tag as well.
html, body {
overflow: hidden;
}
The behaviour you want to prevent is called scroll chaining. To disable it, set
overscroll-behavior: contain;
on your overlay in CSS.
You can easily do this with some "new" css and JQuery.
Initially: body {... overflow:auto;}
With JQuery you can dynamically switch between 'overlay' and 'body'. When on 'body', use
body {
position: static;
overflow: auto;
}
When on 'overlay' use
body {
position: sticky;
overflow: hidden;
}
JQuery for the switch('body'->'overlay'):
$("body").css({"position": "sticky", "overflow": "hidden"});
JQuery for the switch('overlay'->'body'):
$("body").css({"position": "static", "overflow": "auto"});
if anyone is looking for a solution for React function components, you can put this inside the modal component:
useEffect(() => {
document.body.style.overflowY = 'hidden';
return () =>{
document.body.style.overflowY = 'auto';
}
}, [])
Generally speaking, if you want a parent (the body in this case) to prevent it from scrolling when a child (the overlay in this case) scrolls, then make the child a sibling of the parent to prevent the scroll event from bubbling up to the parent. In case of the parent being the body, this requires an additional wrapping element:
<div id="content">
</div>
<div id="overlay">
</div>
See Scroll particular DIV contents with browser's main scrollbar to see its working.
The chosen answer is correct, but has some limitations:
Super hard "flings" with your finger will still scroll <body> in the background
Opening the virtual keyboard by tapping an <input> in the modal will direct all future scrolls to <body>
I don't have a fix for the first issue, but wanted to shed some light on the second. Confusingly, Bootstrap used to have the keyboard issue documented, but they claimed it was fixed, citing http://output.jsbin.com/cacido/quiet as an example of the fix.
Indeed, that example works fine on iOS with my tests. However, upgrading it to the latest Bootstrap (v4) breaks it.
In an attempt to figure out what the difference between them was, I reduced a test case to no longer depend on Bootstrap, http://codepen.io/WestonThayer/pen/bgZxBG.
The deciding factors are bizarre. Avoiding the keyboard issue seems to require that background-color is not set on the root <div> containing the modal and the modal's content must be nested in another <div>, which can have background-color set.
To test it, uncomment the below line in the Codepen example:
.modal {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 2;
display: none;
overflow: hidden;
-webkit-overflow-scrolling: touch;
/* UNCOMMENT TO BREAK */
/* background-color: white; */
}
For touch devices, try adding a 1px wide, 101vh min-height transparent div in the wrapper of the overlay. Then add -webkit-overflow-scrolling:touch; overflow-y: auto; to the wrapper. This tricks mobile safari into thinking the overlay is scrollable, thus intercepting the touch event from the body.
Here's a sample page. Open on mobile safari: http://www.originalfunction.com/overlay.html
https://gist.github.com/YarGnawh/90e0647f21b5fa78d2f678909673507f
I found this question trying to solve issue I had with my page on Ipad and Iphone - body was scrolling when I was displaying fixed div as popup with image.
Some answers are good, however none of them solved my issue. I found following blog post by Christoffer Pettersson. Solution presented there helped issue I had with iOS devices and it helped my scrolling background problem.
Six things I learnt about iOS Safari's rubber band scrolling
As it was suggested I include major points of the blog post in case link gets outdated.
"In order to disable that the user can scroll the background page while the "menu is open", it is possible to control what elements should be allowed to be scrolled or not, by applying some JavaScript and a CSS class.
Based on this Stackoverflow answer you can control that elements with the disable-scrolling should not
perform their default scroll action when the touchmove event is triggered."
document.ontouchmove = function ( event ) {
var isTouchMoveAllowed = true, target = event.target;
while ( target !== null ) {
if ( target.classList && target.classList.contains( 'disable-scrolling' ) ) {
isTouchMoveAllowed = false;
break;
}
target = target.parentNode;
}
if ( !isTouchMoveAllowed ) {
event.preventDefault();
}
};
And then put the disable-scrolling class on the page div:
<div class="page disable-scrolling">
Simple inline styling for the body tag:
<body style="position: sticky; overflow: hidden;">
If the intent is to disable on mobile/ touch devices then the most straightforward way to do it is using touch-action: none;.
Example:
const app = document.getElementById('app');
const overlay = document.getElementById('overlay');
let body = '';
for (let index = 0; index < 500; index++) {
body += index + '<br />';
}
app.innerHTML = body;
app.scrollTop = 200;
overlay.innerHTML = body;
* {
margin: 0;
padding: 0;
}
html,
body {
height: 100%;
}
#app {
background: #f00;
position: absolute;
height: 100%;
width: 100%;
overflow-y: scroll;
line-height: 20px;
}
#overlay {
background: rgba(0,0,0,.5);
position: fixed;
top: 0;
left: 0;
right: 0;
height: 100%;
padding: 0 0 0 100px;
overflow: scroll;
}
<div id='app'></div>
<div id='overlay'></div>
(The example does not work in the context of Stack Overflow. You will need to recreate it in a stand-alone page.)
If you want to disable scrolling of the #app container, just add touch-action: none;.
I'd like to add to previous answers because I tried to do that, and some layout broke as soon as I switched the body to position:fixed. In order to avoid that, I had to also set body's height to 100% :
function onMouseOverOverlay(over){
document.getElementsByTagName("body")[0].style.overflowY = (over?"hidden":"scroll");
document.getElementsByTagName("html")[0].style.position = (over?"fixed":"static");
document.getElementsByTagName("html")[0].style.height = (over?"100%":"auto");
}
Use the following HTML:
<body>
<div class="page">Page content here</div>
<div class="overlay"></div>
</body>
Then JavaScript to intercept and stop scrolling:
$(".page").on("touchmove", function(event) {
event.preventDefault()
});
Then to get things back to normal:
$(".page").off("touchmove");
In my case, none of these solutions worked out on iPhone (iOS 11.0).
The only effective fix that is working on all my devices is this one - ios-10-safari-prevent-scrolling-behind-a-fixed-overlay-and-maintain-scroll-position
try this
var mywindow = $('body'), navbarCollap = $('.navbar-collapse');
navbarCollap.on('show.bs.collapse', function(x) {
mywindow.css({visibility: 'hidden'});
$('body').attr("scroll","no").attr("style", "overflow: hidden");
});
navbarCollap.on('hide.bs.collapse', function(x) {
mywindow.css({visibility: 'visible'});
$('body').attr("scroll","yes").attr("style", "");
});
One solution for a React functional component is to use the useEffect hook.
Here's the code example bellow (pay attention to the useEffect definition):
import {useEffect, useRef} from "react";
export default function PopoverMenu({className, handleClose, children}) {
const selfRef = useRef(undefined);
useEffect(() => {
const isPopoverOpenned = selfRef.current?.style.display !== "none";
const focusedElement = document?.activeElement;
const scrollPosition = {x: window.scrollX, y: window.scrollY};
if (isPopoverOpenned) {
preventDocBodyScrolling();
} else {
restoreDocBodyScrolling();
}
function preventDocBodyScrolling() {
const width = document.body.clientWidth;
const hasVerticalScrollBar = (window.innerWidth > document.documentElement.clientWidth);
document.body.style.overflowX = "hidden";
document.body.style.overflowY = hasVerticalScrollBar ? "scroll" : "";
document.body.style.width = `${width}px`;
document.body.style.position = "fixed";
}
function restoreDocBodyScrolling() {
document.body.style.overflowX = "";
document.body.style.overflowY = "";
document.body.style.width = "";
document.body.style.position = "";
focusedElement?.focus();
window.scrollTo(scrollPosition.x, scrollPosition.y);
}
return () => {
restoreDocBodyScrolling(); // cleanup on unmount
};
}, []);
return (
<>
<div
className="backdrop"
onClick={() => handleClose && handleClose()}
/>
<div
className={`pop-over-menu${className ? (` ${className}`) : ""}`}
ref={selfRef}
>
<button
className="pop-over-menu--close-button" type="button"
onClick={() => handleClose && handleClose()}
>
X
</button>
{children}
</div>
</>
);
}
Originally posted on this other related Stackoverflow question: https://stackoverflow.com/a/69016517/14131330
CSS
.noScroll {
overflow: hidden;
}
Javascript
<script>
function toggleNav() {
document.body.classList.toggle("noScroll");
}
</script>
Button
<button onclick="toggleNav()">
Toggle Nav
</button>
If you want to stop body/html scroll add as the following
CSS
html, body {
height: 100%;
}
.overlay{
position: fixed;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
background-color: rgba(0, 0, 0, 0.8);
.overlay-content {
height: 100%;
overflow: scroll;
}
}
.background-content{
height: 100%;
overflow: auto;
}
HTML
<div class="overlay">
<div class="overlay-content"></div>
</div>
<div class="background-content">
lengthy content here
</div>
Basically, you could do it without JS.
The main idea is to add html/body with height: 100% and overflow: auto.
and inside your overlay, you could either enable/disable scroll based on your requirement.
Hope this helps!
Use below code for disabling and enabling scroll bar.
Scroll = (
function(){
var x,y;
function hndlr(){
window.scrollTo(x,y);
//return;
}
return {
disable : function(x1,y1){
x = x1;
y = y1;
if(window.addEventListener){
window.addEventListener("scroll",hndlr);
}
else{
window.attachEvent("onscroll", hndlr);
}
},
enable: function(){
if(window.removeEventListener){
window.removeEventListener("scroll",hndlr);
}
else{
window.detachEvent("onscroll", hndlr);
}
}
}
})();
//for disabled scroll bar.
Scroll.disable(0,document.body.scrollTop);
//for enabled scroll bar.
Scroll.enable();

Resources