Wordpress Fade in/out on scroll (up and down) - javascript

I am looking to have divs fade in/out when you scroll up/down on my Wordpress site. Variations of this have been found online but not quite what I am looking for.
The code I have managed so far
css
.fade-in-section {
opacity: 0;
transform: translateY(20vh);
visibility: hidden;
transition: opacity 0.6s ease-out, transform 1.2s ease-out;
will-change: opacity, visibility;
}
.fade-in-section.is-visible {
opacity: 1;
transform: none;
visibility: visible;
}
java (although not Wordpress written):
function FadeInSection(props) {
const [isVisible, setVisible] = React.useState(true);
const domRef = React.useRef();
React.useEffect(() => {
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => setVisible(entry.isIntersecting));
});
observer.observe(domRef.current);
return () => observer.unobserve(domRef.current);
}, []);
return (
<div
className={`fade-in-section ${isVisible ? 'is-visible' : ''}`}
ref={domRef}
>
{props.children}
</div>
);
}
( sandbox code and original author here: https://codesandbox.io/s/beautiful-wiles-k23w5?from-embed )
This works perfectly scrolling down, but I would like the same animation/transitioning scrolling up (only 2/3 divs would be visible in the middle of the screen)
Looking for the right approach, help looking for resources to accomplish this.

Try using my solution. Give fade class to elements you want wo fade in/out and apply this:
$(document).ready(function() {
$(window).scroll(function() {
var windowBottom = $(this).scrollTop() + $(this).innerHeight();
$(".fade").each(function() {
/* Check the location of each desired element */
var objectBottom = $(this).offset().top + $(this).outerHeight();
/* If the element is completely within bounds of the window, fade it in */
if (objectBottom < windowBottom) { //object comes into view (scrolling down)
if ($(this).css("opacity")==0) {
$(this).fadeTo(500,1);
}
}else{
if ($(this).css("opacity")==1) {
$(this).fadeTo(500,0);
}
}
});
});
});
.fade {
margin: 50px;
padding: 50px;
background-color: red;
opacity: 0;
width: 150px;
height: 150px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="fade"></div>
<div class="fade"></div>
<div class="fade"></div>
<div class="fade"></div>
<div class="fade"></div>
<div class="fade"></div>
<div class="fade"></div>

jQuery(window).scroll(function () {
var y = jQuery(this).scrollTop();
if (y > 500) {
jQuery('.a2a_floating_style.a2a_vertical_style').show();
} else {
jQuery('.a2a_floating_style.a2a_vertical_style').fadeOut();
}
});

Related

Show div when scrolling using vanilla JavaScript

I'm using vanilla JavaScript and would like to figure out a way where a div container is hidden, but when the user scrolls to 50% of the div container, that's when the div is fully visible. Kind of like a fading-in effect. This is what I have so far:
// delays scroll affects
function debounce(func, wait = 20, immediate = true) {
var timeout;
return function() {
var context = this,
args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
}
// adds animation for section cards
function cardActive(e) {
const cards = document.querySelectorAll('.slide-in');
// checks if image is half shown from bottom
cards.forEach(card => {
const slideInAt = (window.scrollY + window.innerHeight) - card.height / 2;
if (slideInAt > card.offsetTop) {
card.classList.add('active')
} else {
card.classList.remove('active');
}
})
};
window.addEventListener('scroll', debounce(cardActive));
.slide-in {
opacity: 50%;
transition: opacity 0.8s;
}
.slide-in.active {
transition: opacity 0.8s;
opacity: 100%;
visibility: visible !important;
}
.placeholder {
margin-top: 400px;
}
.icon {
width: 100px;
}
<div class="placeholder"></div>
<div class="mission-1 slide-in">
<div class="section-card">
<img class="icon" src="https://image.flaticon.com/icons/svg/869/869767.svg" alt="icon">
<h6 class="mission-card-title">Title 1</h6>
<p class="p-special">Lorem Ipsum.</p>
</div>
</div>
<div class="placeholder"></div>
You need to make two small changes:
You need to replace card.height with card.offsetHeight in your JS file.
Use transition in your CSS file, you do not need animation for a fade in effect:
.slide-in {
opacity: 0%;
transition: opacity 0.8s;
}
.active {
transition: opacity 0.8s;
opacity: 100%;
visibility: visible !important;
}
Here the image is already shown with opacity 1 and then once scrolled over it it will fade using just JS.
// adds animation for section cards
window.addEventListener('scroll', (e) => {
last_known_scroll_position = window.scrollY;
let img = document.getElementById("img-1");
if(img.offsetTop < last_known_scroll_position){
img.style.opacity= 0.1;
}else{
img.style.opacity= 1;
}
});
.slide-in {
opacity: 50%;
}
.slide-in.active {
opacity: 100%;
-webkit-animation: animat_show 0.8s;
animation: animat_show 0.8s;
visibility: visible !important;
}
.lorem {
margin-bottom: 500px;
}
img {
width: 500px;
}
<section class="space">
<p class="lorem">lorem ipsum</p>
<div class="slide-in">
<img class="img-1" id="img-1" src="https://image.flaticon.com/icons/svg/869/869767.svg" alt="confetti">
</div>
<p class="lorem">lorem ipsum</p>
</section>

How I can animate this slideshow(by changing its src)?

Okay okay, so before marking this post as repeated. Let me explain to you:
I made a slideshow in javascript(Vue) and it works by changing its src in an object every time I press a button(next)
It works and all but the problem is that it doesn't get animated no matter what I do, I made a transition on them, set timeout function on it...etc and nothing even the smallest worked.
I could have made another idea which works by the position absolute but I don't want to do that because it will take a loot of time and it will be extremely buggy as position absolute ruins it. So any help on this please?
<template>
<main>
<div id="slideshow">
<figure id="pics">
<img id="slidepic" v-bind:src="pictures[count].src">
<figcaption>{{pictures[count].alt}}</figcaption>
</figure>
</div>
<p>{{count+1}}/{{pictures.length}}</p>
<div id="controls">
<div #click="move(-1)">Previous</div>
<div #click="move(1)">Next</div>
</div>
</main>
Javascript:
methods: {
move: function(num) {
let slideimg = document.querySelector("#slidepic");
slideimg.classList.add("fadeOut");
this.count += num;
if (this.count < 0) {
this.count = this.pictures.length - 1;
} else if (this.count >= this.pictures.length) {
this.count = 0;
}
setTimeout(function() {
slideimg.src = this.pictures[1].src;
}, 1000);
}
}
CSS:
#pics {
opacity: 0.5s;
transition: 0.5s;
}
#pics.fadeOut {
opacity: 1;
}
I didn't include the object(that is in data object, something in Vue) because it would be useless in this situation.
First off all it's transition: <property-name> 0.5s linear; and not transition: 0.5s;. See the transition documentation.
There is no animation for changing the src of an image (see list of animatable css properties).
To do something like this, you can stack all your images into one element and then use css animations and the transform property to create a carousel
var next = document.getElementById('next');
var prev = document.getElementById('prev');
var slideshow = document.getElementById('slideshow');
next.onclick = function() {
var lastChild = slideshow.children[slideshow.children.length - 1];
var firstChild = slideshow.children[0];
var activeEle = document.querySelector('.item.active');
var nextEle = document.querySelector('.item.next');
var prevEle = document.querySelector('.item.prev');
activeEle.classList.remove('active');
activeEle.classList.add('prev');
nextEle.classList.add('active');
nextEle.classList.remove('next');
prevEle.classList.remove('prev');
if (nextEle.nextElementSibling) {
nextEle.nextElementSibling.classList.add('next');
} else {
firstChild.classList.add('next');
}
};
prev.onclick = function() {
var lastChild = slideshow.children[slideshow.children.length - 1];
var activeEle = document.querySelector('.item.active');
var nextEle = document.querySelector('.item.next');
var prevEle = document.querySelector('.item.prev');
// Move the .active class to the previous element
activeEle.classList.remove('active');
activeEle.classList.add('next');
prevEle.classList.add('active');
prevEle.classList.remove('prev');
nextEle.classList.remove('next');
if (prevEle.nextElementSibling) {
prevEle.nextElementSibling.classList.add('prev');
} else {
lastChild.classList.add('prev');
}
};
#slideshow {
width: 100px;
height: 100px;
overflow: hidden;
position: relative;
}
.item {
width: 100%;
height: 100%;
color: white;
background-color: blue;
position: absolute;
/*display: none;*/
top: 0;
left: 0;
z-index: -100;
transition: translateX(-100%);
transition: transform .5s ease-in-out;
opacity: 0;
}
.active {
opacity: 1;
display: block;
z-index: 1;
transform: translateX(0);
}
.next {
transform: translateX(200%);
z-index: 1;
}
.prev {
transform: translateX(-100%);
opacity: 1;
z-index: 1;
}
<div id="slideshow">
<div class="item active">1</div>
<div class="item next">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">5</div>
<div class="item">6</div>
<div class="item prev">7</div>
</div>
<button type="button" id="prev">Prev</button><button type="button" id="next">Next</button>
As you mention you want to build a slideshow on Vue JS, and because jQuery on top of Vue is not recommended, I suggest that you try Vueper Slides, available on NPM. Unless it is for a learning purpose.
I have created two solutions.
First of all. You've a typo.
#pics {
opacity: 0.5s; // <--- remove "s"
transition: 0.5s; // <--- and forgot the property-name (all, opacity ...)
}
#pics.fadeOut {
opacity: 1;
}
I commented all lines I've changed.
Solution
<template>
<main>
<div id="slideshow">
<!--
I recommend to you ref inestad of querySelector.
https://vuejs.org/v2/api/#ref
I've used the v-bind shorthand.
-->
<figure id="pics1" ref="pics1">
<img id="slidepic" :src="pictures[count].src">
<figcaption>{{pictures[count].alt}}</figcaption>
</figure>
<!--
VueJS build-in transition element.
You have to add a key attribute to detect that the content has changed.
I recommend to use this instead of your solution.
It's easier to implement, no class add/remove struggle, its a part of vue, you can add hooks etc.
https://vuejs.org/v2/guide/transitions.html
-->
<transition tag="figure" name="fade" ref="pics2">
<figure id="pics2" :key="`figure-${count}`">
<img :src="pictures[count].src">
<figcaption>{{pictures[count].alt}}</figcaption>
</figure>
</transition>
</div>
<p>{{count+1}}/{{pictures.length}}</p>
<div id="controls">
<div #click="move(-1)">Previous</div>
<div #click="move(1)">Next</div>
</div>
</main>
</template>
<script>
export default {
name: 'teams',
data() {
return {
count: 0,
pictures: [
{
src: 'https://picsum.photos/200/300',
alt: 'test'
},
{
src: 'https://picsum.photos/200/400',
alt: 'test2'
}
]
};
},
methods: {
// instead of move: function(num) {} you can also write move() {}
move(num) {
this.count += num;
if (this.count < 0) {
this.count = this.pictures.length - 1;
} else if (this.count >= this.pictures.length) {
this.count = 0;
}
}
},
// Watch "count" changes and add or remove classes
// you can also add this to your "move" method
watch: {
count() {
// access the reference
const element = this.$refs.pics1;
element.classList.add('fadeOut');
element.classList.remove('fadeIn');
setTimeout(() => {
element.classList.remove('fadeOut');
element.classList.add('fadeIn');
}, 500); // same duration as css transition
}
}
};
</script>
<style scoped lang="scss">
#pics1 {
opacity: 1;
transition: opacity 0.5s;
}
#pics1.fadeIn {
opacity: 1;
}
#pics1.fadeOut {
opacity: 0;
}
// All classes for <transition>
// There are all automatically used by vue
.fade-enter-active {
transition: opacity 0.5s;
}
.fade-leave {
display: none;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
</style>

How to display multiple contents as marquee one after another? Second content should take some time to display

I have two different contents, I want to show the contents as marquee in single line and the contents should display one after another with some delay time duration.
<marquee direction="left">
<label>Label 1 content here</label>
<label>Label 2 content here</label>
</marquee>
Here is my solution, without <marquee> tag, which is deprecated now:
//list of slides to be shown
const content = [
'first slide',
'second slide',
'third slide'
];
let key = 0;
const marquee = $('.marquee');
marquee.on('animationstart', () => {
key = 0;
marquee.text(content[key]);
});
marquee.on('animationiteration', () => {
key++;
if(typeof content[key] === 'undefined') key = 0;
marquee.text(content[key]);
});
marquee.removeClass('paused');
.marquee-container {
width: 100vw;
overflow: hidden;
white-space: nowrap;
}
.marquee {
padding-left: 100vw;
display: inline-block;
animation: marquee 5s linear infinite;
animation-play-state: running;
}
.marquee.paused, .marquee-container:hover .marquee {
animation-play-state: paused;
}
#keyframes marquee {
0% {
transform: translateX(0);
}
100% {
transform: translateX(-100%);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<div class="marquee-container">
<div class="marquee paused"></div>
</div>
Instead of using <marquee> and <label> tags (which you shouldn't use in this context), please try using JavaScript this way:
$(function () {
$(".slider .slide").hide();
$(".slider .slide:first").fadeIn().delay(10000).fadeOut(function () {
$(this).next().fadeIn();
});
setInterval(function () {
$(".slider .slide:first").fadeIn().delay(10000).fadeOut(function () {
$(this).next().fadeIn();
});
}, 20000);
});
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<div class="slider">
<div class="slide"><marquee>Label 1 content here</marquee></div>
<div class="slide"><marquee>Label 2 content here</marquee></div>
</div>
The above is a bare minimal demonstration of what you might like. Let me know if you need more improvement on this. ☺
You may use this way, without marquee, basing on jquery queue and adding a css class to your elements:
$(function () {
var current_button = 0;
$('div.slider .slide:first')
.show(100)
.addClass('active');
setInterval(function() {
if(current_button===$('.slide').length) {
$('div.slider .slide:first')
.show(100)
.addClass('active');
current_button=0;
} else {
$('div.slider .slide')
.hide(100)
.removeClass('active');
$('div.slider .slide:eq(' + current_button + ')')
.show(100)
.addClass('active');
current_button++;
}
},3500)
});
.slide {
float: left;
transform: translateX(400%);
transition: all 7s;
}
.active {
transform: translateX(-350%);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div class="slider">
<div class="slide">Label 1 content here</div>
<div class="slide">Label 2 content here</div>
</div>
EDIT: I think there is a better way, but I try a solution in the code above.
Hope it helps

How to animate endless loop using jquery?

I have been trying using jquery animate to do a running text. But I can't seems to get it run in an endless loop. It always runs one time only..
/* js: */
$(document).ready(function(){
function scroll() {
$('.scroll').animate({
right: $(document).width()
}, 8000, scroll);
}
scroll();
});
/* css: */
.scroll {
position: absolute;
right: -200px;
width: 200px;
}
<!-- html: -->
<div class="scroll">This text be scrollin'!</div>
This is the demo:
https://jsfiddle.net/y9hvr9fa/1/
Do you guys know how to fix it?
So this is what I did:
Precalculate $(document).width() as if a horizontal scroll appears, the width will change in the next iteration
Remove the width you have set for scroll so that the width is only as long as the content - and you would have to give white-space:nowrap to keep the text in a line.
In the animate use the width of the scroll text using $('.scroll').outerWidth()
See demo below and update fiddle here
$(document).ready(function() {
// initialize
var $width = $(document).width();
var $scrollWidth = $('.scroll').outerWidth();
$('.scroll').css({'right': -$scrollWidth + 'px'});
// animate
function scroll() {
$('.scroll').animate({
right: $width
}, 8000, 'linear', function() {
$('.scroll').css({'right': -$scrollWidth + 'px'});
scroll();
});
}
scroll();
});
body {
overflow: hidden;
}
.scroll {
position: absolute;
white-space: nowrap;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="scroll">This text be scrollin'!</div>
Let me know your feedback on this, thanks!
CSS Alternative:
Alternatively you could use a CSS transition like in this CodePen:
https://codepen.io/jamesbarnett/pen/kfmKa
More advanced:
$(document).ready(function(){
var scroller = $('#scroller'); // scroller $(Element)
var scrollerWidth = scroller.width(); // get its width
var scrollerXPos = window.innerWidth; // init position from window width
var speed = 1.5;
scroller.css('left', scrollerXPos); // set initial position
function moveLeft() {
if(scrollerXPos <= 0 - scrollerWidth) scrollerXPos = window.innerWidth;
scrollerXPos -= speed;
scroller.css('left', scrollerXPos);
window.requestAnimationFrame(moveLeft);
}
window.requestAnimationFrame(moveLeft);
});
.scroll {
display: block;
position: absolute;
overflow: visible;
white-space: nowrap;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="scroller" class="scroll">This text be scrollin'!</div>
Dirty solution (my original answer):
In this example this would be a quick fix:
The text is running to the left without ever stopping. Here you will tell the text to always start at that position. (After the time has run up - meaning not necessarily just when it has left the screen)
$(document).ready(function(){
function scroll() {
$('.scroll').css('right', '-200px').animate({
right: $(document).width()
}, 8000, scroll);
}
scroll();
});
I have been trying using jquery animate to do a running text.
You know that the <marquee> HTML element works, right?
Which means you don't need CSS, Javascript or jQuery.
Pure HTML Solution:
<marquee>This text be scrollin'!</marquee>
The <marquee> element includes a large number of optional declarative attributes which control the behaviour of the scrolling text:
behavior
bgcolor
direction
height
hspace
loop
scrollamount
scrolldelay
truespeed
vspace
width
Further Reading:
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/marquee
Note 1:
The resource above correctly notes that:
This feature is no longer recommended. Though some browsers might
still support it, it may have already been removed from the relevant
web standards, may be in the process of being dropped, or may only be
kept for compatibility purposes.
Note 2
The same resource also recommends:
see the compatibility table at the bottom of this page to guide your decision
And... a cursory look at that compatibility table shows that the <marquee> element is as browser-compatible as the most established, most browser-compatible elements which exist today.
I hope it is useful :)
function start() {
new mq('latest-news');
mqRotate(mqr);
}
window.onload = start;
function objWidth(obj) {
if (obj.offsetWidth) return obj.offsetWidth;
if (obj.clip) return obj.clip.width;
return 0;
}
var mqr = [];
function mq(id) {
this.mqo = document.getElementById(id);
var wid = objWidth(this.mqo.getElementsByTagName("span")[0]) + 5;
var fulwid = objWidth(this.mqo);
var txt = this.mqo.getElementsByTagName("span")[0].innerHTML;
this.mqo.innerHTML = "";
var heit = this.mqo.style.height;
this.mqo.onmouseout = function () {
mqRotate(mqr);
};
this.mqo.onmouseover = function () {
clearTimeout(mqr[0].TO);
};
this.mqo.ary = [];
var maxw = Math.ceil(fulwid / wid) + 1;
for (var i = 0; i < maxw; i++) {
this.mqo.ary[i] = document.createElement("div");
this.mqo.ary[i].innerHTML = txt;
this.mqo.ary[i].style.position = "absolute";
this.mqo.ary[i].style.left = wid * i + "px";
this.mqo.ary[i].style.width = wid + "px";
this.mqo.ary[i].style.height = heit;
this.mqo.appendChild(this.mqo.ary[i]);
}
mqr.push(this.mqo);
}
function mqRotate(mqr) {
if (!mqr) return;
for (var j = mqr.length - 1; j > -1; j--) {
maxa = mqr[j].ary.length;
for (var i = 0; i < maxa; i++) {
var x = mqr[j].ary[i].style;
x.left = parseInt(x.left, 10) - 1 + "px";
}
var y = mqr[j].ary[0].style;
if (parseInt(y.left, 10) + parseInt(y.width, 10) < 0) {
var z = mqr[j].ary.shift();
z.style.left = parseInt(z.style.left) + parseInt(z.style.width) * maxa + "px";
mqr[j].ary.push(z);
}
}
mqr[0].TO = setTimeout("mqRotate(mqr)", 20);
}
.marquee {
position: relative;
overflow: hidden;
text-align: center;
margin: 0 auto;
width: 100%;
height: 30px;
display: flex;
align-items: center;
white-space: nowrap;
}
#latest-news {
line-height: 32px;
a {
color: #555555;
font-size: 13px;
font-weight: 300;
&:hover {
color: #000000;
}
}
span {
font-size: 18px;
position: relative;
top: 4px;
color: #999999;
}
}
<div id="latest-news" class="marquee">
<span style="white-space:nowrap;">
<span> •</span>
one Lorem ipsum dolor sit amet
<span> •</span>
two In publishing and graphic design
<span> •</span>
three Lorem ipsum is a placeholder text commonly
</span>
</div>
How is this?
.scroll {
height: 50px;
overflow: hidden;
position: relative;
}
.scroll p{
position: absolute;
width: 100%;
height: 100%;
margin: 0;
line-height: 50px;
text-align: center;
-moz-transform:translateX(100%);
-webkit-transform:translateX(100%);
transform:translateX(100%);
-moz-animation: scroll 8s linear infinite;
-webkit-animation: scroll 8s linear infinite;
animation: scroll 8s linear infinite;
}
#-moz-keyframes scroll {
0% { -moz-transform: translateX(100%); }
100% { -moz-transform: translateX(-100%); }
}
#-webkit-keyframes scroll {
0% { -webkit-transform: translateX(100%); }
100% { -webkit-transform: translateX(-100%); }
}
#keyframes scroll {
0% {
-moz-transform: translateX(100%);
-webkit-transform: translateX(100%);
transform: translateX(100%);
}
100% {
-moz-transform: translateX(-100%);
-webkit-transform: translateX(-100%);
transform: translateX(-100%);
}
}
<div class="scroll"><p>This text be scrollin'!</p></div>

Using CSS transform scale() to zoom into an element without cropping, maintaining scrolling

Live example: https://jsfiddle.net/b8vLg0ny/
It's possible to use the CSS scale and translate functions to zoom into element.
Take this example, of 4 boxes in a 2x2 grid.
HTML:
<div id="container">
<div id="zoom-container">
<div class="box red">A</div>
<div class="box blue">B</div>
<div class="box green">C</div>
<div class="box black">D</div>
</div>
</div>
CSS:
* { margin: 0; }
body, html { height: 100%; }
#container {
height: 100%;
width: 50%;
margin: 0 auto;
}
#zoom-container {
height: 100%;
width: 100%;
transition: all 0.2s ease-in-out;
}
.box {
float: left;
width: 50%;
height: 50%;
color: white;
text-align: center;
display: block;
}
.red { background: red; }
.blue { background: blue; }
.green { background: green; }
.black { background: black; }
JavaScript:
window.zoomedIn = false;
$(".box").click(function(event) {
var el = this;
var zoomContainer = $("#zoom-container");
if (window.zoomedIn) {
console.log("resetting zoom");
zoomContainer.css("transform", "");
$("#container").css("overflow", "auto");
window.zoomedIn = false;
} else {
console.log("applying zoom");
var top = el.offsetTop;
var left = el.offsetLeft - 0.25*zoomContainer[0].clientWidth;
var translateY = 0.5*zoomContainer[0].clientHeight - top;
var translateX = 0.5*zoomContainer[0].clientWidth - left;
$("#container").css("overflow", "scroll");
zoomContainer.css("transform", "translate(" + 2 * translateX + "px, " + 2 * translateY + "px) scale(2)");
window.zoomedIn = true;
}
});
By controlling the value of translateX and translateY, you can change how the zooming works.
The initial rendered view looks something like this:
Clicking on the A box will zoom you in appropriately:
(Note that clicking D at the end is just showing the reset by zooming back out.)
The problem is: zooming to box D will scale the zoom container such that scrolling to the top and left doesn't work, because the contents overflow. The same happens when zooming to boxes B (the left half is cropped) and C (the top half is cropped). Only with A does the content not overflow outside the container.
In similar situations related to scaling (see CSS3 Transform Scale and Container with Overflow), one possible solution is to specify transform-origin: top left (or 0 0). Because of the way the scaling works relative to the top left, the scrolling functionality stays. That doesn't seem to work here though, because it means you're no longer repositioning the contents to be focused on the clicked box (A, B, C or D).
Another possible solution is to add a margin-left and a margin-top to the zoom container, which adds enough space to make up for the overflowed contents. But again: the translate values no longer line up.
So: is there a way to both zoom in on a given element, and overflow with a scroll so that contents aren't cropped?
Update: There's a rough almost-solution by animating scrollTop and scrollLeft, similar to https://stackoverflow.com/a/31406704/528044 (see the jsfiddle example), but it's not quite a proper solution because it first zooms to the top left, not the intended target. I'm beginning to suspect this isn't actually possible, because it's probably equivalent to asking for scrollLeft to be negative.
Why not just to reposition the TransformOrigin to 0 0 and to use proper scrollTop/scrollLeft after the animation?
https://jsfiddle.net/b8vLg0ny/7/
Updated: https://jsfiddle.net/b8vLg0ny/13/
If you do not need the animation, the TransformOrigin can always stays 0 0 and only the scrolling is used to show the box.
To make the animation less jumpy use transition only for transform porperty, otherwise the transform-origin gets animated also. I have edited the example with 4x4 elements, but I think it makes sense to zoom a box completely into view, thats why I changed the zoom level. But if you stay by zoom level 2 and the grid size 15x15 for instance, then with this approach really precise origin should be calculated for transform, and then also the correct scrolling.
Anyway I don't know, if you find this approach useful.
Stack snippet
var zoomedIn = false;
var zoomContainer = $("#zoom-container");
$(".box").click(function(event) {
var el = this;
if (zoomedIn) {
zoomContainer.css({
transform: "scale(1)",
transformOrigin: "0 0"
});
zoomContainer.parent().scrollTop(0).scrollLeft(0);
zoomedIn = false;
return;
}
zoomedIn = true;
var $el = $(el);
animate($el);
zoomContainer.on('transitionend', function(){
zoomContainer.off('transitionend');
reposition($el);
})
});
var COLS = 4, ROWS = 4,
COLS_STEP = 100 / (COLS - 1), ROWS_STEP = 100 / (ROWS - 1),
ZOOM = 4;
function animate($box) {
var cell = getCell($box);
var col = cell.col * COLS_STEP + '%',
row = cell.row * ROWS_STEP + '%';
zoomContainer.parent().css('overflow', 'hidden');
zoomContainer.css({
transition: 'transform 0.2s ease-in-out',
transform: "scale(" + ZOOM + ")",
transformOrigin: col + " " + row
});
}
function reposition($box) {
zoomContainer.css({
transition: 'none',
transform: "scale(" + ZOOM + ")",
transformOrigin: '0 0'
});
zoomContainer.parent().css('overflow', 'auto');
$box.get(0).scrollIntoView();
}
function getCell ($box) {
var idx = $box.index();
var col = idx % COLS,
row = (idx / ROWS) | 0;
return { col: col, row: row };
}
* { margin: 0; }
body, html { height: 100%; }
#container {
height: 100%;
width: 50%;
margin: 0 auto;
overflow: hidden;
}
#zoom-container {
height: 100%;
width: 100%;
will-change: transform;
}
.box {
float: left;
width: 25%;
height: 25%;
color: white;
text-align: center;
}
.red { background: red; }
.blue { background: blue; }
.green { background: green; }
.black { background: black; }
.l { opacity: .3 }
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.2.2/jquery.min.js"></script>
<div id="container">
<div id="zoom-container">
<div class="box red">A</div>
<div class="box blue">B</div>
<div class="box green">C</div>
<div class="box black">D</div>
<div class="box red l">E</div>
<div class="box blue l">F</div>
<div class="box green l">G</div>
<div class="box black l">H</div>
<div class="box red">I</div>
<div class="box blue">J</div>
<div class="box green">K</div>
<div class="box black">L</div>
<div class="box red l">M</div>
<div class="box blue l">N</div>
<div class="box green l">O</div>
<div class="box black l">P</div>
</div>
</div>
I'm answering my own question, since I'm fairly confident that it's actually not possible with the given requirements. At least not without some hackery that would cause problems visually, e.g., jumpy scrolling by animating scrollTop after switching transform-origin to 0, 0 (which removes the cropping by bringing everything back into the container).
I'd love for someone to prove me wrong, but it seems equivalent to asking for scrollLeft = -10, something that MDN will tell you is not possible. ("If set to a value less than 0 [...], scrollLeft is set to 0.")
If, however, it's acceptable to change the UI from scrolling, to zooming and dragging/panning, then it's achievable: https://jsfiddle.net/jegn4x0f/5/
Here's the solution with the same context as my original problem:
HTML:
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.2.2/jquery.min.js"></script>
<button id="zoom-out">Zoom out</button>
<div id="container">
<div id="inner-container">
<div id="zoom-container">
<div class="box red">A</div>
<div class="box blue">B</div>
<div class="box green">C</div>
<div class="box black">D</div>
</div>
</div>
</div>
JavaScript:
//
// credit for the approach goes to
//
// https://stackoverflow.com/questions/35252249/move-drag-pan-and-zoom-object-image-or-div-in-pure-js#comment58224460_35253567
//
// and the corresponding example:
//
// https://jsfiddle.net/j8kLz6wm/1/
//
// in a real-world setting, you
// wouldn't keep this information
// on window. this is just for
// the demonstration.
window.zoomedIn = false;
// stores the initial translate values after clicking on a box
window.translateY = null;
window.translateX = null;
// stores the incremental translate values based on
// applying the initial translate values + delta
window.lastTranslateY = null;
window.lastTranslateX = null;
// cursor position relative to the container, at
// the time the drag started
window.dragStartX = null;
window.dragStartY = null;
var handleDragStart = function(element, xCursor, yCursor) {
window.dragStartX = xCursor - element.offsetLeft;
window.dragStartY = yCursor - element.offsetTop;
// disable transition animations, since we're starting a drag
$("#zoom-container").css("transition", "none");
};
var handleDragEnd = function() {
window.dragStartX = null;
window.dragStartY = null;
// remove the individual element's styling for transitions
// which brings back the stylesheet's default of animating.
$("#zoom-container").css("transition", "");
// keep track of the translate values we arrived at
window.translateY = window.lastTranslateY;
window.translateX = window.lastTranslateX;
};
var handleDragMove = function(xCursor, yCursor) {
var deltaX = xCursor - window.dragStartX;
var deltaY = yCursor - window.dragStartY;
var translateY = window.translateY + (deltaY / 2);
// the subtracted value here is to keep the letter in the center
var translateX = window.translateX + (deltaX / 2) - (0.25 * $("#inner-container")[0].clientWidth);
// fudge factor, probably because of percentage
// width/height problems. couldn't really trace down
// the underlying cause. hopefully the general approach
// is clear, though.
translateY -= 9;
translateX -= 4;
var innerContainer = $("#inner-container")[0];
// cap all values to prevent infinity scrolling off the page
if (translateY > 0.5 * innerContainer.clientHeight) {
translateY = 0.5 * innerContainer.clientHeight;
}
if (translateX > 0.5 * innerContainer.clientWidth) {
translateX = 0.5 * innerContainer.clientWidth;
}
if (translateY < -0.5 * innerContainer.clientHeight) {
translateY = -0.5 * innerContainer.clientHeight;
}
if (translateX < -0.5 * innerContainer.clientWidth) {
translateX = -0.5 * innerContainer.clientWidth;
}
// update the zoom container's translate values
// based on the original + delta, capped to the
// container's width and height.
$("#zoom-container").css("transform", "translate(" + (2*translateX) + "px, " + (2*translateY) + "px) scale(2)");
// keep track of the updated values for the next
// touchmove event.
window.lastTranslateX = translateX;
window.lastTranslateY = translateY;
};
// Drag start -- touch version
$("#container").on("touchstart", function(event) {
if (!window.zoomedIn) {
return true;
}
var xCursor = event.originalEvent.changedTouches[0].clientX;
var yCursor = event.originalEvent.changedTouches[0].clientY;
handleDragStart(this, xCursor, yCursor);
});
// Drag start -- mouse version
$("#container").on("mousedown", function(event) {
if (!window.zoomedIn) {
return true;
}
var xCursor = event.clientX;
var yCursor = event.clientY;
handleDragStart(this, xCursor, yCursor);
});
// Drag end -- touch version
$("#inner-container").on("touchend", function(event) {
if (!window.zoomedIn) {
return true;
}
handleDragEnd();
});
// Drag end -- mouse version
$("#inner-container").on("mouseup", function(event) {
if (!window.zoomedIn) {
return true;
}
handleDragEnd();
});
// Drag move -- touch version
$("#inner-container").on("touchmove", function(event) {
// prevent pull-to-refresh. could be smarter by checking
// if the page's scroll y-offset is 0, and even smarter
// by checking if we're pulling down, not up.
event.preventDefault();
if (!window.zoomedIn) {
return true;
}
var xCursor = event.originalEvent.changedTouches[0].clientX;
var yCursor = event.originalEvent.changedTouches[0].clientY;
handleDragMove(xCursor, yCursor);
});
// Drag move -- click version
$("#inner-container").on("mousemove", function(event) {
// prevent pull-to-refresh. could be smarter by checking
// if the page's scroll y-offset is 0, and even smarter
// by checking if we're pulling down, not up.
event.preventDefault();
// if we aren't dragging from anywhere, don't move
if (!window.zoomedIn || !window.dragStartX) {
return true;
}
var xCursor = event.clientX;
var yCursor = event.clientY;
handleDragMove(xCursor, yCursor);
});
var zoomInTo = function(element) {
console.log("applying zoom");
var top = element.offsetTop;
// the subtracted value here is to keep the letter in the center
var left = element.offsetLeft - (0.25 * $("#inner-container")[0].clientWidth);
var translateY = 0.5 * $("#zoom-container")[0].clientHeight - top;
var translateX = 0.5 * $("#zoom-container")[0].clientWidth - left;
$("#container").css("overflow", "scroll");
$("#zoom-container").css("transform", "translate(" + (2*translateX) + "px, " + (2*translateY) + "px) scale(2)");
window.translateY = translateY;
window.translateX = translateX;
window.zoomedIn = true;
}
var zoomOut = function() {
console.log("resetting zoom");
window.zoomedIn = false;
$("#zoom-container").css("transform", "");
$("#zoom-container").css("transition", "");
window.dragStartX = null;
window.dragStartY = null;
window.dragMoveJustHappened = null;
window.translateY = window.lastTranslateY;
window.translateX = window.lastTranslateX;
window.lastTranslateX = null;
window.lastTranslateY = null;
}
$(".box").click(function(event) {
var element = this;
var zoomContainer = $("#zoom-container");
if (!window.zoomedIn) {
zoomInTo(element);
}
});
$("#zoom-out").click(function(event) {
zoomOut();
});
CSS:
* {
margin: 0;
}
body,
html {
height: 100%;
}
#container {
height: 100%;
width: 50%;
margin: 0 auto;
}
#inner-container {
width: 100%;
height: 100%;
}
#zoom-container {
height: 100%;
width: 100%;
transition: transform 0.2s ease-in-out;
}
.box {
float: left;
width: 50%;
height: 50%;
color: white;
text-align: center;
display: block;
}
.red {
background: red;
}
.blue {
background: blue;
}
.green {
background: green;
}
.black {
background: black;
}
I pieced this together from another question (Move (drag/pan) and zoom object (image or div) in pure js), where the width and height are being changed. That doesn't quite apply in my case, because I need to zoom into a specific element on the page (with a lot boxes than in a 2x2 grid). The solution from that question (https://jsfiddle.net/j8kLz6wm/1/) shows the basic approach in pure JavaScript. If you have jQuery available, you can probably just use jquery.panzoom.
Update
I got stuck on scroll bars not showing all the time, so I need to investigating that part, so that code is commented out and instead I use a delay to move the clicked box into view.
Here is my fiddle demo, which I use to play with, to figure out how to solve the scroll bar issue.
Side note: In a comment made by #AVAVT, I would like to link to his post here, as that might help someone else, which I find as an interesting alternative in some cases.
(function(zoomed) {
$(".box").click(function(event) {
var el = this, elp = el.parentElement;
if (zoomed) {
zoomed = false;
$("#zoom-container").css({'transform': ''});
} else {
zoomed = true;
/* this zooms correct but show 1 or none scroll for B,C,D so need to figure out why
var tro = (Math.abs(elp.offsetTop - el.offsetTop) > 0) ? 'bottom' : 'top';
tro += (Math.abs(elp.offsetLeft - el.offsetLeft) > 0) ? ' right' : ' left';
$("#zoom-container").css({'transform-origin': tro, 'transform': 'scale(2)'});
*/
$("#zoom-container").css({'transform-origin': '0 0', 'transform': 'scale(2)'});
/* delay needed before scroll into view */
setTimeout(function() {
el.scrollIntoView();
},250);
}
});
})();
* { margin: 0; }
body, html { height: 100%; }
#container {
height: 100%;
width: 50%;
overflow: auto;
margin: 0 auto;
}
#zoom-container {
height: 100%;
width: 100%;
transition: all 0.2s ease-in-out;
}
.box {
float: left;
width: 50%;
height: 50%;
color: white;
text-align: center;
display: block;
}
.red {
background: red;
}
.blue {
background: blue;
}
.green {
background: green;
}
.black {
background: black;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.2.2/jquery.min.js"></script>
<div id="container">
<div id="zoom-container">
<div class="box red">A</div>
<div class="box blue">B</div>
<div class="box green">C</div>
<div class="box black">D</div>
</div>
</div>

Categories