My question is related to my previous question Triggering css animate class on scroll
I am trying to figure out how to trigger the viewport Javascript on the inner SVG element class.
You can see here the example: http://codepen.io/anon/pen/Afqza
<div style="height: 400px;"></div>
<svg version="1.1" id="lock" xmlns="http://www.w3.org/2000/svg" width="85px" height="85px" viewBox="0 0 103 103" ><g><g><g><g><circle style="fill:#E84849;" cx="51.5" cy="51.501" r="51.125"/></g></g></g></g><g><g><g class="shackle"><path style="fill:#CFC7BE;" d="M78.345,46.518c0-14.869-11.813-28.387-26.386-28.387c-14.573,0-26.386,13.518-26.386,28.387h6.829c0-11.021,8.756-21.419,19.557-21.419s19.557,10.398,19.557,21.419H78.345z"/><path style="fill:#E8E7E7;" d="M61.385,20.101v7.816c3.039,1.927,5.583,4.717,7.362,7.975V24.879C66.562,22.886,64.076,21.26,61.385,20.101z"/></g><g><path style="fill:#F4E028;" d="M78.358,79.801c0,3.116-2.579,5.642-5.765,5.642H31.281c-3.186,0-5.764-2.525-5.764-5.642V46.419c0-3.116,52.841-3.116,52.841,0V79.801z"/></g><g><path style="fill:#DAC425;" d="M58.047,59.944c0-3.253-2.638-5.89-5.889-5.89c-3.253,0-5.889,2.637-5.889,5.89c0,2.481,1.536,4.599,3.705,5.468v5.431c0,1.151,0.935,2.084,2.085,2.084c1.153,0,2.086-0.933,2.086-2.084v-5.36C56.418,64.666,58.047,62.498,58.047,59.944z"/></g><g><path style="fill:#D0B82B;" d="M46.048,59.944c0-3.253,2.637-5.89,5.891-5.89c0,0-4.105,2.737-4.105,5.99c0,3.312,3.097,5.276,3.097,5.276v5.581c0,1.153,1.104,2.024,1.104,2.024c-1.15,0-2.085-0.933-2.085-2.084v-5.36C47.677,64.666,46.048,62.498,46.048,59.944z"/></g></g><g><polygon style="fill:#F8E349;" points="68.747,85.442 61.385,85.442 61.385,44.219 68.747,44.585 "/></g></g></svg>
.shackle {
animation-name: open;
-webkit-animation-name: open;
animation-duration: 0.5s;
-webkit-animation-duration: 0.5s;
transform: rotate(0deg);
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
-webkit-transform: rotate(0deg);
transform-origin: bottom left;
}
#keyframes open {
0% {
transform: rotate(0deg);
}
50% {
transform: rotate(-10deg);
}
100% {
transform: rotate(0deg);
}
}
#-webkit-keyframes open {
0% {
-webkit-transform: rotate(0deg);
}
50% {
-webkit-transform: rotate(-10deg);
}
100% {
-webkit-transform: rotate(0deg);
}
}
var onAppear = [];
document.addEventListener("DOMContentLoaded", function() {
onAppear = [].map.call(document.querySelectorAll("#lock"), function(item ) {
return item;
});
}, false);
window.addEventListener("scroll", function() {
onAppear.forEach(function(elem) {
var vwTop = window.pageYOffset;
var vwBottom = (window.pageYOffset + window.innerHeight);
var elemTop = elem.offsetTop;
var elemHeight = elem.offsetHeight;
if (vwBottom > elemTop && ((vwTop - elemHeight) < elemTop)) {
elem.classList.add("shackle");
} else {
elem.classList.remove("shackle");
}
});
}, false);
Currently the whole padlock animates instead of the shackle that I want to animate.
Must be something simple but I cannot figure it out.
The issue is that you arent applying the animation class to the shackle element, you are applying it to the lock element.
Because you are playing around with CSS3 and SVG, I can assume you dont need to accommodate for IE7 and below. Therefore, we can assume it's safe to use JS's querySelector method.
First, we'll update the style definition to indicate the class is for an animation definition (and also to separate it from the class on the shackle element that we'll use to select it).
change .shackle to .animShackle in CSS
Second, we'll need to update the scroll event listener to search within the supplied element for the .shackle classed element, and then apply the animation class to that.
update JS
JS
window.addEventListener("scroll", function() {
onAppear.forEach(function(elem) {
var vwTop = window.pageYOffset;
var vwBottom = (window.pageYOffset + window.innerHeight);
var elemTop = elem.offsetTop;
var elemHeight = elem.offsetHeight;
var shackle = elem.querySelector('.shackle');
if (vwBottom > elemTop && ((vwTop - elemHeight) < elemTop)) {
shackle.classList.add("animShackle");
} else {
shackle.classList.remove("animShackle");
}
});
}, false);
UPDATE
To make the code more extensible to the need for additional elements with their own animations we need to change some of our variable names so that they feel more universal, and update the way we are getting and setting the animation class.
Add a universal class to the animated SVG's so that we can find them in our onAppear function
add class animatedSVG
update querySelectorAll method to use new class rather than single id
Update the class name on the animated element within the SVG so that we can access it within the scroll onAppear.forEach method
update class .shackle to .animatedElement in HTML
update elem.querySelector method to use new class rather than non-generic .shackle
Use the SVG's id attribute to create a classname for animation
add a new variable called animationClass made from the SVG id with 'anim' prepended
HTML now requires the following 3 things:
id attribute on SVG
class="animatedSVG" on SVG
class="animatedElement" on element within SVG you wish to animate
Updated JS
var onAppear = [];
document.addEventListener("DOMContentLoaded", function() {
onAppear = [].map.call(document.querySelectorAll(".animatedSVG"), function(item) {
return item;
});
}, false);
window.addEventListener("scroll", function() {
onAppear.forEach(function(elem) {
var vwTop = window.pageYOffset;
var vwBottom = (window.pageYOffset + window.innerHeight);
var elemTop = elem.offsetTop;
var elemHeight = elem.offsetHeight;
var animatedElem = elem.querySelector('.animatedElement');
var animationClass = 'anim'+elem.id;
if (vwBottom > elemTop && ((vwTop - elemHeight) < elemTop)) {
animatedElem.classList.add(animationClass);
} else {
animatedElem.classList.remove(animationClass);
}
});
}, false);
DEMO
Related
I am developing this site: https://studioboom.superhi.com/
Is it possible to apply the image click to only the first section, so once scrolled you are able to click on links rather than add an image.
JS:
const images = [
'benjones_flip1.jpg',
'benjones_home1.jpg',
'ben_jones_ts2.jpg',
'benjones_gs1.jpg',
'benjones_jt1.jpg',
'benjones_dlf4.jpg'
]
let i = 0
function placeImage(x, y) {
const nextImage = images[i]
const img = document.createElement('img')
img.classList.add('external-loaded-img')
img.setAttribute('src', nextImage)
img.style.left = x + 'px'
img.style.top = y + 'px'
document.body.appendChild(img)
i = i + 1
if (i >= images.length) {
i = 0
}
}
document.addEventListener('click', function(event) {
event.preventDefault()
placeImage(event.pageX, event.pageY)
})
document.addEventListener('touchend', function(event) {
event.preventDefault()
placeImage(event.pageX, event.pageY)
})
.external-loaded-img {
position: absolute;
transform: translate(-50%, -50%) scale(0.5);
animation: fadein 0.5s;
z-index: 10;
overflow: hidden;
}
#keyframes fadein {
0% {opacity: 0;}
100% {opacity: 1;}
}
Either wait to set up your event handlers until the container of the elements you want them to apply to are present in the page, and then hook up the handlers on that container:
container.addEventListner('click', /*...*/);
container.addEventListner('touchend', /*...*/);
...or keep them hooked up on document, but check when the event occurs that it passed through that container ("event delegation"):
document.addEventListner('click', function(event) {
if (!event.target.closest("selector-for-the-container")) {
return;
}
// ...
});
document.addEventListner('touchend', /*...*/);
if (!event.target.closest("selector-for-the-container")) {
return;
}
// ...
});
More on closest on MDN, including information about polyfills if needed.
document.querySelector(".about").addEventListener('click', function(event) {
event.preventDefault()
placeImage(event.pageX, event.pageY)
})
Something like this does work, if I am clicking on section.photo1, if I clicking on an actual image, it doesn't. Is there a work around for this?
document.addEventListener('click', function(event) {
if (!event.target.closest("section.photo1")) {
return;
}
event.preventDefault()
placeImage(event.pageX, event.pageY)
})
I'm trying to follow this tutorial for my portfolio website.
I've almost got it working but instead of only fading in images as they come into the window, it is fading the images that are already in the window.
$(document).on("scroll", function () {
var pageTop = $(document).scrollTop()
var pageBottom = pageTop + $(window).height()
var tags = $("section")
for (var i = 0; i < tags.length; i++) {
var tag = tags[i]
if ($(tag).position().top < pageBottom) {
$(tag).addClass("visible")
} else {
$(tag).removeClass("visible")
}
}
})
section {
opacity: 0;
transform: translate(0, 20px);
transition: all 1.5s;
}
section.visible {
opacity: 1;
transform: translate(0, 0);
}
This is because everything is hidden until the first scroll event fires. To fix this you can manually trigger a scroll event when the page first loads in order to display the section elements which are already visible in the viewport.
$(document).on("scroll", function () {
// your code here...
}).trigger('scroll');
It's also worth noting that the scroll event handler fires for every pixel that you scroll by. As such performance is important there so it would be worth optimising that handler function.
var $tags = $("section");
var winHeight = $(window).height();
$(document).on("scroll", function() {
var pageTop = $(document).scrollTop();
var pageBottom = pageTop + winHeight;
$tags.each(function() {
this.classList.toggle(this.offsetTop < pageBottom)
});
}).trigger('scroll');
$(window).on('resize', function() {
winHeight = $(this).height();
});
I currently have a script set up to add a class to a div once its in the viewport. I have multiple divs that this applies to however, so once the first one is visible, the class gets added to every single one. Is there a more streamline way of separating these rather than duplicating the function for each element?
HTML
<div class="header-title"><span>FOO</span></div>
<div class="header-title"><span>BAR</span></div>
CSS
.header-title span {
display: inline-block;
position: relative;
}
.change:after {
content: " ";
position: absolute;
height: 5px;
border-bottom: 1px solid red;
-webkit-animation: extend .75s 1 forwards;
animation: extend 4s 1 forwards;
margin-left: 4px;
top: 1.2em !important;
}
#-webkit-keyframes extend {
0% {
width: 0;
}
100% {
width: 200px;
}
}
#keyframes extend {
0% {
width: 0;
}
100% {
width: 200px;
}
}
jQuery
function isElementInViewport(elem) {
var $elem = jQuery(elem);
// Get the scroll position of the page.
var scrollElem = ((navigator.userAgent.toLowerCase().indexOf('webkit') != -1) ? 'body' : 'html');
var viewportTop = jQuery(scrollElem).scrollTop();
var viewportBottom = viewportTop + jQuery(window).height();
// Get the position of the element on the page.
var elemTop = Math.round( $elem.offset().top ) ;
var elemBottom = elemTop + $elem.height();
return ((elemTop < viewportBottom) && (elemBottom > viewportTop));
}
// Check if it's time to start the animation.
function extendLine() {
var $elem = jQuery('.header-title span');
// If the animation has already been started
if ($elem.hasClass('change')) return;
if (isElementInViewport($elem)) {
// Start the animation
$elem.addClass('change');
}
}
// Capture scroll events
jQuery(window).scroll(function(){
extendLine();
});
http://codepen.io/SeanLindsay1/pen/bBOWLW
You're running the function for all instances of .header-title span. Instead, do each individually:
function extendLine() {
jQuery('.header-title span').each(function() {
var $elem = this;
...
});
}
Demo
I have div, which has some attributes style.
<div class="js__scroll__canvas" style="width: 10054px; transition-timing-function: cubic-bezier(0.1, 0.57, 0.1, 1); transition-duration: 0ms; transform: translate(0px, 0px) translateZ(0px); height: 521px;">
Another plugin js changes transform: translate(0px, 0px) value (bold) - so x position.
I try to catch this value change.
var currentPos = parseInt($(".scroll-container .js__scroll__canvas").attr('style','transform').split(',')[4]);
$(document).on('change', currentPos , function() {
alert( "Handler for .change() called." );
});
But nothing. I try to output currentPos value, but nothing returns, not null, or array, or anything else.
I see unusual structure of style attr transform - transform: translate(0px, 0px) translateZ(0px);. May be problem in that?
Help me please!
I think you want .css not .attr:
var currentPos = parseInt($(".scroll-container .js__scroll__canvas").css('transform').split(',')[4])
The code you have would set the style attribute to "transform":
.attr('style','transform')
Also, this won't work:
$(document).on('change', currentPos , function() {
The second argument to on should be a JQuery selector, not a number. Try this:
var element = $(".scroll-container .js__scroll__canvas");
var currentPos = parseInt(element.css('transform').split(',')[4]);
element.on('change', function() {
var newPos = parseInt(element.css('transform').split(',')[4]);
if(currentPos != newPos) {
currentPos = newPos;
alert("It changed!");
}
});
I need to detect if a user is hovering over an element, which is straightforward. However, these events don't seem to fire when the element is animating. If you check out my fiddle, just have the element animate past your mouse without moving your mouse, and you'll see that the events don't fire. It makes sense why this would happen, but I haven't been able to find a good way to get the behavior I want, which is to detect hovering even if the user doesn't move his/her mouse and the element animates under it.
Any thoughts?
Thanks!
Note: solutions without use of external libraries are optimal, but any help is still appreciated :)
HTML
<div id='moving'></div>
<ul id="message"></ul>
CSS
#moving {
width: 50px;
height: 50px;
background-color: red;
animation: move 7s linear;
}
#keyframes move {
from {transform: translateX(0px)}
to {transform: translateX(500px)}
}
JS
var counter = 0;
document.getElementById("moving").addEventListener("mouseover", function(){
counter++;
var node = document.createElement("LI");
var textnode = document.createTextNode("Entered " + counter);
node.appendChild(textnode);
document.getElementById("message").appendChild(node);
});
document.getElementById("moving").addEventListener("mouseout", function(){
var node = document.createElement("LI");
var textnode = document.createTextNode("Left " + counter);
node.appendChild(textnode);
document.getElementById("message").appendChild(node);
});
Here's a fiddle of it:
https://jsfiddle.net/w5j842Lx/
You can check if the mouse is in or out within an interval. Here is a working fiddle extending from your fiddle.
// This is the helper method I have written
var addMoveListener = function(element, onmouseover, onmouseout) {
var over = false;
var mouseX, mouseY;
var checkOver = function(ev) {
if (ev) {
mouseX = ev.clientX;
mouseY = ev.clientY;
}
if (mouseX == null || mouseY == null) return;
var rect = element.getBoundingClientRect();
var isInside = mouseX >= rect.left && mouseX < rect.right && mouseY >= rect.top && mouseY < rect.bottom;
if (over && !isInside && onmouseout) onmouseout();
if (!over && isInside && onmouseover) onmouseover();
over = isInside;
}
document.addEventListener("mousemove", checkOver);
var interval = setInterval(checkOver.bind(null, null), 100);
}
// Code below is for the sake of demonstration
var counter = 0;
var mouseovercallback = function() {
counter++;
console.log("Entered " + counter);
};
var mouseoutcallback = function() {
console.log("Left " + counter);
};
addMoveListener(document.getElementById("moving"), mouseovercallback, mouseoutcallback);
#moving {
width: 50px;
height: 50px;
background-color: red;
animation: move 7s linear;
}
#keyframes move {
from {
transform: translateX(0px)
}
to {
transform: translateX(500px)
}
}
<div id='moving'></div>
The code checks if the mouse is contained for every 100 miliseconds and also if the mouse is moved. If you want to handle cases where the element is not a rectangle or is rotated, skewed etc., you have to improve the code.
Take a look at this jsfiddle https://jsfiddle.net/3vpaoj59/
It includes a function like this
setInterval(checkMouse, 100);
that basically calls a function 10 times a second to check if the mouse's coordinates are within the animated shape. Your shape is a square and not a circle, so you would have to do some different math. This code is nice because it doesn't use a plugin, but it's probably CPU intensive and might have poor performance in some cases.