Padding aspect ratio hack, but based on height - javascript

Most of you know a trick that makes element scale at a fixed aspect ratio, for example:
<div style="width:100%;position:relative">
<div style="padding-bottom:100%;background:tomato;height:0;">square (1:1)</div>
</div>
(jsfiddle https://jsfiddle.net/0dda7939/ )
I want the same thing, but based on height not based on width.
<div style="height:100%;position:relative">
<div style="??? background:tomato;">square (1:1)</div>
</div>
Is there any way to do this without JS and without using VW/VH units (since parent container might not always be window)?

With JS it can be done easily. The problem with doing by CSS is applying width equal to the height. If you are comfortable to use SASS, then you can do that. Otherwise, JS (jQuery) solution is given below:
HTML:
<div class="parent">
<div class="child">I am a square (1:1)</div>
</div>
CSS:
.parent {
border: 1px solid black;
width:auto;
height: 200px;
position:relative
}
.child {
background:tomato;
height:inherit;
}
JS:
let parentWidth = $('.parent').width();
let parentHeight = $('.parent').height();
let childWidth = $('.child').width();
let childHeight = $('.child').height();
let minSize = 0;
$('.child').width(childHeight);
$(window).on('resize', function () {
parentWidth = $('.parent').width();
parentHeight = $('.parent').height();
childWidth = $('.child').width();
childHeight = $('.child').height();
minSize = Math.min(parentWidth, parentHeight);
if (childWidth >= parentWidth || childHeight >= parentHeight) {
if (childWidth >= parentWidth && childHeight >= parentHeight) {
$('.child').height(minSize);
$('.child').width(minSize);
} else if (childWidth >= parentWidth) {
$('.child').width(parentWidth);
$('.child').height(parentWidth);
} else {
$('.child').height(parentHeight);
$('.child').width(parentHeight);
}
} else {
$('.child').height(minSize);
$('.child').width(minSize);
}
});

Try adding this code::
<div style="display: table; width: 100%; height: 100%; text-align: center;">
<div style="display: table-cell; vertical-align: middle; background:tomato;">square (1:1)</div>
</div>

Related

Check if child element is 100% visible inside a parent div that has overflow hidden

Good day,
I have a dynamic carousel that I want to check if the first slide and the last slide is 100% visible for the user and then it needs to trigger a function, currently I'm using getBoundingClientRect() but it uses the viewport and not the parent div. the parent div is not full width but is 80% of the viewport
This is my code to check the first slide, and it works with viewport:
JavaScript:
function isInViewport(el) {
const rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}
const box = document.querySelector('.firstSlide');
const message = document.querySelector('#inView');
console.log(box);
document.addEventListener('click', function () {
const messageText = isInViewport(box) ?
'Yess its in view' :
'Yess its not in view';
message.textContent = messageText;
}, {
passive: true
});
HTML:
<div class="carousel" style="grid-template-columns: repeat(5, 585px); overfow:hidden, width:80%; margin:auto">
<div class="image-container"><img src="image/url" alt=""></div>
<div class="image-container"><img src="image/url" alt=""></div>
<div class="image-container"><img src="image/url" alt=""></div>
<div class="image-container"><img src="image/url" alt=""></div>
<div class="image-container firstSlide"><img src="image/url" alt=""></div>
</div>
<p id="inView">Yess its in view</p>
Is there a way so that it check if the child element is 100% in view of the parent element and not the entire viewport?
There is two ways to solve this issue.
If you want to use getBoundingClientRect you can get the bounding box for the parent element. Calculate where this box is in the viewport. Then instead of comparing where the slide is compared to the viewbox, compare it to the boundingbox of the parent.
(Best practice) The new fancy way. Use the Intersection Observer API, that api can be used to check if elements are in the viewport but can also be used directly to see where a element is compared to a ancestry element. The new api is also running by the browser and will not block the main thread which is really good for performance.
Here is a link to the Intersection Observer Api: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
The following code can determine if one bounding client rectangle is completely inside another.
const d1 = document.getElementById('d1');
const d2 = document.getElementById('d2');
const p1 = document.getElementById('p1');
const p2 = document.getElementById('p2');
const r1 = document.getElementById('r1');
const r2 = document.getElementById('r2');
function isPVisible(element, container)
{
const elRect = element.getBoundingClientRect();
const conRect = container.getBoundingClientRect();
let result = false;
if(elRect.x >= conRect.x && elRect.y >= conRect.y
&& elRect.x + elRect.width <= conRect.x + conRect.width
&& elRect.y + elRect.height <= conRect.y + conRect.height)
{
result = true
}
return result;
}
console.log(isPVisible(p1, d1));
console.log(isPVisible(p2, d2));
#d1
{
width: 40px;
height: 10px;
overflow: hidden;
border: 1px blue solid;
}
#p1
{
width: 50px;
height: 30px;
border: 1px red solid;
}
#d2
{
width: 90px;
height: 50px;
overflow: hidden;
border: 1px blue solid;
}
#p2
{
width: 50px;
height: 30px;
border: 1px red solid;
}
<div id="d1">
<p id="p1">hello</p>
</div>
<div id="d2">
<p id="p2">hello</p>
</div>
<p>
<span id="r1"></span>
<span id="r2"></span>
</p>

Multiple sticky sections with horizontal scrolling

I'm trying to make a page with multiple sticky sections with horizontal scrolling (so when you're scrolling vertically as normal, you're forced to go through the horizontal gallery)
I'm referencing this codepen (https://codepen.io/johnhubler/pen/RwoPRBG) as my JS knowledge is very poor. But, as you can see in the codepen, it is only working in the first sticky section, and the second one stays still.
var windowWidth = window.innerWidth;
var horLength = document.querySelector(".element-wrapper").scrollWidth;
var horLength2 = document.querySelector(".element-wrapper2").scrollWidth;
var distFromTop = document.querySelector(".horizontal-section").offsetTop;
var distFromTop2 = document.querySelector(".horizontal-section2").offsetTop;
var scrollDistance = distFromTop + horLength - windowWidth;
var scrollDistance2 = distFromTop2 + horLength2 - windowWidth;
document.querySelector(".horizontal-section").style.height = horLength + "px";
document.querySelector(".horizontal-section2").style.height = horLength2 + "px";
window.onscroll = function(){
var scrollTop = window.pageYOffset;
if (scrollTop >= distFromTop && scrollTop <= scrollDistance) {
document.querySelector(".element-wrapper").style.transform = "translateX(-"+(scrollTop - distFromTop)+"px)";
}
if (scrollTop >= distFromTop2 && scrollTop <= scrollDistance2) {
document.querySelector(".element-wrapper2").style.transform = "translateX(-"+(scrollTop - distFromTop2)+"px)";
}
}
I'm planning to add around 4 of the same sticky sections, so I'd like to know how to make it work in all of them. If there is a better alternative/resource/etc.(if possible, vanilla JS or something very easy to follow) please let me know.
Thank you
I made an optimized and working version of your code.
This array lists the classes of packaging elements. This way you can add as many galleries as you want by simply adding a new class to the array.
var array = ['.horizontal-section', '.horizontal-section2'];
Example:
var array = ['.horizontal-section', '.horizontal-section2'];
window.onscroll = function () {
var windowWidth = window.innerWidth;
var scrollTop = window.pageYOffset;
array.forEach(el => {
var wrap = document.querySelector(el);
var elWrap = wrap.querySelector(".element-wrapper");
var horLength = elWrap.scrollWidth;
var distFromTop = wrap.offsetTop;
var scrollDistance = distFromTop + horLength - windowWidth;
wrap.style.height = horLength + "px";
if (scrollTop >= distFromTop && scrollTop <= scrollDistance) {
elWrap.style.transform = "translateX(-" + (scrollTop - distFromTop) + "px)";
}
});
}
* {
box-sizing: border-box;
}
body {
padding: 0;
margin: 0;
}
.bumper {
width: 100%;
height: 1800px;
background-color: #f3f3f3;
}
.horizontal-section,
.horizontal-section2 {
padding: 100px 0;
background-color: pink;
}
.sticky-wrapper,
.sticky-wrapper2 {
position: sticky;
top: 100px;
width: 100%;
overflow: hidden;
}
.element-wrapper,
.element-wrapper2 {
position: relative;
display: flex;
}
.element {
width: 500px;
height: 400px;
background-color: purple;
margin: 0 20px 0 0;
flex-shrink: 0;
}
<div class="bumper"></div>
<div class="horizontal-section">
<div class="sticky-wrapper">
<div class="element-wrapper">
<div class="element"></div>
<div class="element"></div>
<div class="element"></div>
<div class="element"></div>
<div class="element"></div>
</div>
</div>
</div>
<div class="bumper"></div>
<div class="horizontal-section2">
<div class="sticky-wrapper">
<div class="element-wrapper">
<div class="element"></div>
<div class="element"></div>
<div class="element"></div>
<div class="element"></div>
<div class="element"></div>
</div>
</div>
</div>
<div class="bumper"></div>

How to move to the next/previous slides on a CSS carousel?

I'm building a carousel with CSS
.carousel {
-webkit-scroll-snap-type: x mandatory;
-ms-scroll-snap-type: x mandatory;
scroll-snap-type: x mandatory;
-webkit-overflow-scrolling: touch;
overflow-x: scroll;
scrollbar-width: none;
-ms-overflow-style: none;
width: 500px;
display: flex;
flex-direction: row;
outline: 1px #000 solid;
}
.slide {
background-color: #ccc;
width: 100%;
height: 100px;
scroll-snap-align: start;
flex-shrink: 0
}
.prev {
translatex(-500px)
}
.next {
translatex(500px)
}
<div class="carousel">
<div class="slide">1</div>
<div class="slide">2</div>
<div class="slide">3</div>
</div>
<button type="button">prev</button>
<button type="button">next</button>
It works fine in modern browsers. Is there a way to also add prev and next arrows? I assume it will trigger some JS that will move to the next slide, but how to know which one is the current slide with this approach?
This reminded me that I had some old carousel code kicking around from...2017! Wow, long time.
I had to puzzle it out for a minute. Looks like I have some resize code to make the images fit inside the container. In this case, the images are 512px wide but the container is 300px wide, so they scale.
It has a show method that can be attached to buttons. It takes two parameters: an int to say how many images to move (negative goes backwards) and a boolean that indicates if it should wrap around when it hits the end.
It manipulates the margin offset to show the each "slide". The advantage of this is the CSS transition that makes them animate.
var Slides;
(function(Slides) {
let slides_container = document.querySelector(".slides-container");
let slides = document.querySelector(".slides");
let images = slides.querySelectorAll("img");
let img_idx = 0;
for (let ix = 0; ix < images.length; ix++) {
images[ix].addEventListener("load", function() {
scaleElementToAncestor(this, slides_container);
});
}
function show(next, wrap) {
if (!slides.children[img_idx + next]) {
if (!wrap)
return;
img_idx = img_idx + next;
if (img_idx < 0)
img_idx = slides.children.length - 1;
if (img_idx > slides.children.length - 1)
img_idx = 0;
} else {
img_idx = img_idx + next;
}
let offset = 0;
for (let ix = 0; ix < img_idx; ix++) {
offset += slides.children[ix].clientWidth;
}
slides.style.marginLeft = -offset + "px";
}
Slides.show = show;
function scaleElementToAncestor(el, ancestor) {
const max_width = ancestor.clientWidth;
const max_height = ancestor.clientHeight;
const initial_width = el.clientWidth;
const initial_height = el.clientHeight;
let width = (max_height * initial_width) / initial_height;
let height = (max_width * initial_height) / initial_width;
if (width > max_width)
width = (height * initial_width) / initial_height;
if (height > max_height)
height = (width * initial_height) / initial_width;
el.style.width = width + "px";
el.style.height = height + "px";
}
})(Slides || (Slides = {}));
.slides-container {
font-size: 0;
width: 300px;
height: 150px;
overflow: hidden;
white-space: nowrap;
margin: 0 auto;
}
.slides {
display: inline-block;
-webkit-transition: margin-left 0.5s;
transition: margin-left 0.5s;
}
.slides img {
vertical-align: top;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/skeleton/2.0.4/skeleton.min.css" rel="stylesheet" />
<div class="container">
<div class="row">
<div class="slides-container">
<!-- add class -->
<div class="slides" onclick="Slides.show(1, true)">
<!-- add class -->
<!-- add click-handler, or not -->
<img src="https://dummyimage.com/512x256/000/fff">
<img src="https://dummyimage.com/512x128/f00/fff">
<img src="https://dummyimage.com/512x256/0f0/000">
<img src="https://dummyimage.com/256x256/00f/fff">
<img src="https://dummyimage.com/512x256/999/000">
</div>
</div>
<div style="margin-top: 1em; text-align: center;">
<button onclick="Slides.show(-1, false)">Previous</button>
<!-- add click-handler -->
<button onclick="Slides.show(1, true)">Next</button>
<!-- add click-handler -->
</div>
</div>
</div>

How detect which child element is visible after scrolling the parent div?

I would like to emulate something like "current page" using divs (like a PDF reader)
document.addEventListener("DOMContentLoaded", function(event) {
var container = document.getElementById("container");
container.onscroll = function() {
let position = container.scrollTop;
let divs = document.querySelectorAll('.page');
for (div of divs) {
//???
}
}
});
#container {
width: 400px;
height: 600px;
overflow: auto;
}
.page {
width: 400px;
}
.red {
background-color: red;
height: 600px;
}
.blue {
background-color: blue;
height: 400px;
}
Current page: <span id="page-counter">1</span>
<div id='container'>
<div id="div-1" class="page red"></div>
<div id="div-2" class="page blue"></div>
<div id="div-3" class="page red"></div>
<div id="div-4" class="page blue"></div>
</div>
So, I would like to know the best way to, for example, change span page-counter text to "3" when the third div "appears".
Something like this: https://i.imgur.com/rXQ2Bw8.png
Thanks in advance
Celso
Since this question never tagged jQuery, here's a pure Javascript solution that simulates the behavior you're looking for to the best of my knowledge. The solution calculates the amount of pixels of each child element currently visible within the container. If the amount is bigger or equal to half the size of the container, it assumes this is the page your visitor is looking at.
function getVisibleHeight(element){
const container = document.getElementById("container");
let scrollTop = container.scrollTop;
let scrollBot = scrollTop + container.clientHeight;
let containerRect = container.getBoundingClientRect();
let eleRect = element.getBoundingClientRect();
let rect = {};
rect.top = eleRect.top - containerRect.top,
rect.right = eleRect.right - containerRect.right,
rect.bottom = eleRect.bottom - containerRect.bottom,
rect.left = eleRect.left - containerRect.left;
let eleTop = rect.top + scrollTop;
let eleBot = eleTop + element.offsetHeight;
let visibleTop = eleTop < scrollTop ? scrollTop : eleTop;
let visibleBot = eleBot > scrollBot ? scrollBot : eleBot;
return visibleBot - visibleTop;
}
document.addEventListener("DOMContentLoaded", function(event) {
const container = document.getElementById("container");
const divs = document.querySelectorAll('.page');
container.addEventListener("scroll", () => {
for(let i=0; i<divs.length; i++){
const containerHeight = container.clientHeight;
// Gets the amount of pixels currently visible within the container
let visiblePageHeight = getVisibleHeight(divs[i]);
// If the amount of visible pixels is bigger or equal to half the container size, set page
if(visiblePageHeight >= containerHeight / 2){
document.getElementById('page-counter').innerText = i+1;
}
}
}, false);
});
#container {
width: 400px;
height: 300px;
overflow: auto;
}
.page {
width: 380px;
}
.red {
background-color: red;
height: 300px;
}
.blue {
background-color: blue;
height: 200px;
}
Current page: <span id="page-counter">1</span>
<div id='container'>
<div id="div-1" class="page red"></div>
<div id="div-2" class="page blue"></div>
<div id="div-3" class="page red"></div>
<div id="div-4" class="page blue"></div>
</div>
The general approach here would be to write a function that determines if a given HTML element is in the viewport. You could run the check as the user scrolls. See the snippet below for an example with jQuery. I'm not necessarily saying this is the best way to do this, but it seems to be working. Start scrolling to see the IDs appear.
function isInViewPort(element) {
// Function will determine if any part of the element is in the viewport.
let $el = $("#" + element);
let windowScrollTop = $(window).scrollTop();
let windowHeight = $(window).height();
let windowBottom = windowScrollTop + windowHeight;
let elementTop = $el.offset().top;
let elementOuterHeight = $el.outerHeight();
let elementBottom = elementTop + elementOuterHeight;
let isAboveViewPort = elementBottom < windowScrollTop;
let isBelowViewPort = windowBottom < elementTop;
return !(isAboveViewPort || isBelowViewPort);
}
let currentDiv;
$("#container").on("scroll", function() {
$("#container").find("div").each(function() {
if (isInViewPort(this.id) && currentDiv !== this.id) {
$("#page").html("Current ID is " + this.id)
currentDiv = this.id;
}
});
});
#container {
overflow: auto;
height: 300px;
}
.red {
background-color: red;
height: 600px;
}
.blue {
background-color: blue;
height: 400px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<span id="page"></span>
<div id='container'>
<div id="div-1" class="page red"></div>
<div id="div-2" class="page blue"></div>
<div id="div-3" class="page red"></div>
<div id="div-4" class="page blue"></div>
</div>
you can use the is visible feature in jQuery. Just give each div a unique ID or class.
if( $("#uniqueIdHere").is(':visible'))
$(".page3Selector").addClass('active');
and then to remove the active class you could pair it up with an else statement to remove the class of the inactive div.

animate opacity in and out on scroll

So I have a set of elements called .project-slide, one after the other. Some of these will have the .colour-change class, IF they do have this class they will change the background colour of the .background element when they come into view. This is what I've got so far: https://codepen.io/neal_fletcher/pen/eGmmvJ
But I'm looking to achieve something like this: http://studio.institute/clients/nike/
Scroll through the page to see the background change. So in my case what I'd want is that when a .colour-change was coming into view it would slowly animate the opacity in of the .background element, then slowly animate the opacity out as I scroll past it (animating on scroll that is).
Any suggestions on how I could achieve that would be greatly appreciated!
HTML:
<div class="project-slide fullscreen">
SLIDE ONE
</div>
<div class="project-slide fullscreen">
SLIDE TWO
</div>
<div class="project-slide fullscreen colour-change" data-bg="#EA8D02">
SLIDE THREE
</div>
<div class="project-slide fullscreen">
SLIDE TWO
</div>
<div class="project-slide fullscreen colour-change" data-bg="#cccccc">
SLIDE THREE
</div>
</div>
jQuery:
$(window).on('scroll', function () {
$('.project-slide').each(function() {
if ($(window).scrollTop() >= $(this).offset().top - ($(window).height() / 2)) {
if($(this).hasClass('colour-change')) {
var bgCol = $(this).attr('data-bg');
$('.background').css('background-color', bgCol);
} else {
}
} else {
}
});
});
Set some data-gb-color with RGB values like 255,0,0…
Calculate the currently tracked element in-viewport-height.
than get the 0..1 value of the inViewport element height and use it as the Alpha channel for the RGB color:
/**
* inViewport jQuery plugin by Roko C.B.
* http://stackoverflow.com/a/26831113/383904
* Returns a callback function with an argument holding
* the current amount of px an element is visible in viewport
* (The min returned value is 0 (element outside of viewport)
*/
;
(function($, win) {
$.fn.inViewport = function(cb) {
return this.each(function(i, el) {
function visPx() {
var elH = $(el).outerHeight(),
H = $(win).height(),
r = el.getBoundingClientRect(),
t = r.top,
b = r.bottom;
return cb.call(el, Math.max(0, t > 0 ? Math.min(elH, H - t) : (b < H ? b : H)), H);
}
visPx();
$(win).on("resize scroll", visPx);
});
};
}(jQuery, window));
// OK. Let's do it
var $wrap = $(".background");
$("[data-bg-color]").inViewport(function(px, winH) {
var opacity = (px - winH) / winH + 1;
if (opacity <= 0) return; // Ignore if value is 0
$wrap.css({background: "rgba(" + this.dataset.bgColor + ", " + opacity + ")"});
});
/*QuickReset*/*{margin:0;box-sizing:border-box;}html,body{height:100%;font:14px/1.4 sans-serif;}
.project-slide {
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.project-slide h2 {
font-weight: 100;
font-size: 10vw;
}
<div class="project-slides-wrap background">
<div class="project-slide">
<h2>when in trouble...</h2>
</div>
<div class="project-slide" data-bg-color="0,200,255">
<h2>real trouble...</h2>
</div>
<div class="project-slide">
<h2>ask...</h2>
</div>
<div class="project-slide" data-bg-color="244,128,36">
<h2>stack<b>overflow</b></h2>
</div>
</div>
<script src="//code.jquery.com/jquery-3.1.0.js"></script>
Looks like that effect is using two fixed divs so if you need something simple like that you can do it like this:
But if you need something more complicated use #Roko's answer.
var fixed = $(".fixed");
var fixed2 = $(".fixed2");
$( window ).scroll(function() {
var top = $( window ).scrollTop();
var opacity = (top)/300;
if( opacity > 1 )
opacity = 1;
fixed.css("opacity",opacity);
if( fixed.css('opacity') == 1 ) {
top = 0;
opacity = (top += $( window ).scrollTop()-400)/300;
if( opacity > 1 )
opacity = 1;
fixed2.css("opacity",opacity);
}
});
.fixed{
display: block;
width: 100%;
height: 200px;
background: blue;
position: fixed;
top: 0px;
left: 0px;
color: #FFF;
padding: 0px;
margin: 0px;
opacity: 0;
}
.fixed2{
display: block;
width: 100%;
height: 200px;
background: red;
position: fixed;
top: 0px;
left: 0px;
color: #FFF;
padding: 0px;
margin: 0px;
opacity: 0;
}
.container{
display: inline-block;
width: 100%;
height: 2000px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
Scroll me!!
</div>
<div class="fixed">
</div>
<div class="fixed2">
</div>

Categories

Resources