So I got an element with the following CSS:
top: 0;
left: 0;
width: 100%;
height: 300px;
border-radius: 0;
box-shadow: none;
transition: all .3s ease-in-out;
On click I am trying to add/subtract 100% from the left property depending on which button is clicked:
$('.next, .prev').click(function() {
if ($(this).hasClass("next")) {
$(".element").animate({
left: '-=100%'
}, 500);
}
else if ($(this).hasClass("prev")) {
$(".element").animate({
left: '+=100%'
}, 500);
}
});
When I click one of the buttons, it works fine, but on the 2nd press, it jumps to a percentage, which doesn't makes sense to me, i.e. 1002.22%, -802.222, ...
You got a full example here after clicking an element to open the content.
Does anyone know why it's not acting the way I want to?
There is no way to retrieve an elements css value as percentage, so when you try to add or subtract further it is doing this to the pixel value. I found this out when trying to do the subtraction outside of the animate call.
$('.portfolio-next, .portfolio-prev').click(function() {
var leftVal = parseFloat($(".duplicated .dupAnim").css('left'));
if ($(this).hasClass("portfolio-next")) {
leftVal = (leftVal - 100) + '%';
}
else if ($(this).hasClass("portfolio-prev")) {
leftVal = (leftVal + 100) + '%';
}
$(".duplicated .dupAnim").animate({
left: leftVal
}, 500);
});
The above code does not work, but illustrates a similar issue where the returned value is not a percentage.
In order to solve this you will probably need to keep a count (in a separate attribute or variable) of the number of times it has been added or subtracted. This is a hack and is probably not how I would choose to do it.
Alternatively there is an answer here about calculating the percentage from the pixel value that css() returns.
Related
This question already has answers here:
How can I force WebKit to redraw/repaint to propagate style changes?
(33 answers)
Closed 4 years ago.
Currently I am working on an animation for a website which involves two elements having their position changed over a period of time and usually reset to their initial position. Only one element will be visible at a time and everything ought to run as smoothly as possible.
Before you ask, a CSS-only solution is not possible as it is dynamically generated and must be synchronised. For the sake of this question, I will be using a very simplified version which simply consists of a box moving to the right. I shall be referring only to this latter example unless explicitly stated for the remainder of this question to keep things simple.
Anyway, the movement is handled by the CSS transition property being set so that the browser can do the heavy lifting for that. This transition must then be done away with in order to reset the element's position in an instant. The obvious way of doing so would be to do just that then reapply transition when it needs to get moving again, which is also right away. However, this isn't working. Not quite. I'll explain.
Take a look at the JavaScript at the end of this question or in the linked JSFiddle and you can see that is what I'm doing, but setTimeout is adding a delay of 25ms in between. The reason for this is (and it's probably best you try this yourself) if there is either no delay (which is what I want) or a very short delay, the element will either intermittently or continually stay in place, which isn't the desired effect. The higher the delay, the more likely it is to work, although in my actual animation this causes a minor jitter because the animation works in two parts and is not designed to have a delay.
This does seem like the sort of thing that could be a browser bug but I've tested this on Chrome, Firefox 52 and the current version of Firefox, all with similar results. I'm not sure where to go from here as I have been unable to find this issue reported anywhere or any solutions/workarounds. It would be much appreciated if someone could find a way to get this reliably working as intended. :)
Here is the JSFiddle page with an example of what I mean.
The markup and code is also pasted here:
var box = document.getElementById("box");
//Reduce this value or set it to 0 (I
//want rid of the timeout altogether)
//and it will only function correctly
//intermittently.
var delay = 25;
setInterval(function() {
box.style.transition = "none";
box.style.left = "1em";
setTimeout(function() {
box.style.transition = "1s linear";
box.style.left = "11em";
}, delay);
}, 1000);
#box {
width: 5em;
height: 5em;
background-color: cyan;
position: absolute;
top: 1em;
left: 1em;
}
<div id="box"></div>
Force the DOM to recalculate itself before setting a new transition after reset. This can be achieved for example by reading the offset of the box, something like this:
var box = document.getElementById("box");
setInterval(function(){
box.style.transition = "none";
box.style.left = "1em";
let x = box.offsetLeft; // Reading a positioning value forces DOM to recalculate all the positions after changes
box.style.transition = "1s linear";
box.style.left = "11em";
}, 1000);
body {
background-color: rgba(0,0,0,0);
}
#box {
width: 5em;
height: 5em;
background-color: cyan;
position: absolute;
top: 1em;
left: 1em;
}
<div id="box"></div>
See also a working demo at jsFiddle.
Normally the DOM is not updated when you set its properties until the script will be finished. Then the DOM is recalculated and rendered. However, if you read a DOM property after changing it, it forces a recalculation immediately.
What happens without the timeout (and property reading) is, that the style.left value is first changed to 1em, and then immediately to 11em. Transition takes place after the script will be fihished, and sees the last set value (11em). But if you read a position value between the changes, transition has a fresh value to go with.
Instead of making the transition behave as an animation, use animation, it will do a much better job, most importantly performance-wise and one don't need a timer to watch it.
With the animation events one can synchronize the animation any way suited, including fire of a timer to restart or alter it.
Either with some parts being setup with CSS
var box = document.getElementById("box");
box.style.left = "11em"; // start
box.addEventListener("animationend", animation_ended, false);
function animation_ended (e) {
if (e.type == 'animationend') {
this.style.left = "1em";
}
}
#box {
width: 5em;
height: 5em;
background-color: cyan;
position: absolute;
top: 1em;
left: 1em;
animation: move_me 1s linear 4;
}
#keyframes move_me {
0% { left: 1em; }
}
<div id="box"></div>
Or completely script based
var prop = 'left', value1 = '1em', value2 = '11em';
var s = document.createElement('style');
s.type = 'text/css';
s.innerHTML = '#keyframes move_me {0% { ' + prop + ':' + value1 +' }}';
document.getElementsByTagName('head')[0].appendChild(s);
var box = document.getElementById("box");
box.style.animation = 'move_me 1s linear 4';
box.style.left = value2; // start
box.addEventListener("animationend", animation_ended, false);
function animation_ended (e) {
if (e.type == 'animationend') {
this.style.left = value1;
}
}
#box {
width: 5em;
height: 5em;
background-color: cyan;
position: absolute;
top: 1em;
left: 1em;
}
<div id="box"></div>
The closest solution I found is Show div on scrollDown after 800px.
I'm learning HTML, CSS, and JS, and I decided to try to make a digital flipbook: a simple animation that would play (ie, load frame after frame) on the user's scroll.
I figured I would add all the images to the HTML and then use CSS to "stack them" in the same position, then use JS or jQuery to fade one into the next at different points in the scroll (ie, increasing pixel distances from the top of the page).
Unfortunately, I can't produce the behavior I'm looking for.
HTML (just all the frames of the animation):
<img class="frame" id="frame0" src="images/hand.jpg">
<img class="frame" id="frame1" src="images/frame_0_delay-0.13s.gif">
CSS:
body {
height: 10000px;
}
.frame {
display: block;
position: fixed;
top: 0px;
z-index: 1;
transition: all 1s;
}
#hand0 {
padding: 55px 155px 55px 155px;
background-color: white;
}
.frameHide {
opacity: 0;
left: -100%;
}
.frameShow {
opacity: 1;
left: 0;
}
JS:
frame0 = document.getElementById("frame0");
var myScrollFunc = function() {
var y = window.scrollY;
if (y >= 800) {
frame0.className = "frameShow"
} else {
frame0.className = "frameHide"
}
};
window.addEventListener("scroll", myScrollFunc);
};
One of your bigger problems is that setting frame0.className = "frameShow" removes your initial class frame, which will remove a bunch of properties. To fix this, at least in a simple way, we can do frame0.className = "frame frameShow", etc. Another issue is that frame0 is rendered behind frame1, which could be fixed a variety of ways. ie. Putting frame0's <img> after frame1, or setting frame0's CSS to have a z-index:2;, and then setting frame0's class to class="frame frameHide" so it doesn't show up to begin with. I also removed the margin and padding from the body using CSS, as it disturbs the location of the images. I have made your code work the way I understand you wanted it to, here is a JSFiddle.
It depends on your case, for example, in this jsFiddle 1 I'm showing the next (or previous) frame depending on the value of the vertical scroll full window.
So for my case the code is:
var jQ = $.noConflict(),
frames = jQ('.frame'),
win = jQ(window),
// this is very important to be calculated correctly in order to get it work right
// the idea here is to calculate the available amount of scrolling space until the
// scrollbar hits the bottom of the window, and then divide it by number of frames
steps = Math.floor((jQ(document).height() - win.height()) / frames.length),
// start the index by 1 since the first frame is already shown
index = 1;
win.on('scroll', function() {
// on scroll, if the scroll value equal or more than a certain number, fade the
// corresponding frame in, then increase index by one.
if (win.scrollTop() >= index * steps) {
jQ(frames[index]).animate({'opacity': 1}, 50);
index++;
} else {
// else if it's less, hide the relative frame then decrease the index by one
// thus it will work whether the user scrolls up or down
jQ(frames[index]).animate({'opacity': 0}, 50);
index--;
}
});
Update:
Considering another scenario, where we have the frames inside a scroll-able div, then we wrap the .frame images within another div .inner.
jsFiddle 2
var jQ = $.noConflict(),
cont = jQ('#frames-container'),
inner = jQ('#inner-div'),
frames = jQ('.frame'),
frameHeight = jQ('#frame1').height(),
frameWidth = jQ('#frame1').width() + 20, // we add 20px because of the horizontal scroll
index = 0;
// set the height of the outer container div to be same as 1 frame height
// and the inner div height to be the sum of all frames height, also we
// add some pixels just for safety, 20px here
cont.css({'height': frameHeight, 'width': frameWidth});
inner.css({'height': frameHeight * frames.length + 20});
cont.on('scroll', function() {
var space = index * frameHeight;
if (cont.scrollTop() >= space) {
jQ(frames[index]).animate({'opacity': 1}, 0);
index++;
} else {
jQ(frames[index]).animate({'opacity': 0}, 0);
index--;
}
});
** Please Note that in both cases all frames must have same height.
The following function takes an options parameter and animates the element to a particular amount of pixels based on a startValue:
options: {
property: 'right',
startValue: '-250px',
amount: '250px'
},
function (options) {
const $el = $(this.el)
$el.click(() => {
const startValue = $el.parent().css(slide.property)
const calcValue = parseInt(startValue, 10) + parseInt(slide.amount, 10)
if (startValue === slide.startValue) {
$el.parent().animate({ [slide.property]: calcValue }, 200)
} else {
$el.parent().animate({ [slide.property]: slide.startValue }, 200)
}
})
}
But I'm wondering, is it possible to accomplish the same without having to provide the startValue to the function? (e.g if the initial value of right was 0 then restore it to 0 the second time you click the element.)
You can take advantage of the fact that .animate() is adding an inline style attribute when it is called. So if you want to restore an element back to the right value specified in your CSS, you can call .removeAttr("style"). To get the animated effect, you would have to include a transition property in your CSS.
For example, check out this working fiddle: https://jsfiddle.net/hr0cxax2/1/
$("#slideButton").on("click", function() {
$("div").animate({right:"-=50px"}, 200);
});
$("#restoreButton").on("click", function() {
$("div").removeAttr("style");
});
div {
height: 50px;
width: 50px;
top: 20px;
right: 300px;
background-color: red;
position: fixed;
-webkit-transition: right 0.2s;
-moz-transition: right 0.2s;
transition: right 0.2s;
}
Otherwise, as far as I know, you would need to get and save the original right value before .animate() is called.
I have the following div floating but I want as the green element inside left panel to have a delay about half second.
Does anyone any idea how can I do this?
https://jsfiddle.net/eoopvgmc/22/
This is the code which is floating the elements on scroll
$(document).ready(function() {
var offset = $('.ads').offset().top, top;
$(document).on('scroll', function() {
top = $(window).scrollTop() < offset ? '0' : $(window).scrollTop() - offset + 'px';
$('.ads').css({
'top': top
});
})
});
To make the .element independent transition, you need to move it out of the .left-zone element.
$(document).ready(function () {
var offset = $('.ads').offset().top,
top;
$(document).on('scroll', function () {
top = $(window).scrollTop() < offset ? '0' : $(window).scrollTop() - offset;
console.log(top);
$('.ads').css({
'top': top
});
$('.element').css({
'top': +top + 50
});
})
});
Working Fiddle
I managed to get something like what you describe working by adding some transition effects to the element and using a little delay, you should be able to tweak the timeout, margin-top and transition values to get exactly what you want:
$(document).ready(function() {
var offset = $('.ads').offset().top, top;
$(document).on('scroll', function() {
top = $(window).scrollTop() < offset ? '0' : $(window).scrollTop() - offset + 'px';
$('.ads .element').css({
'transition': 'none',
'margin-top': '-60px'
});
$('.ads').css({
'top': top
});
setTimeout(function() {
$('.ads .element').css({
'transition': 'margin-top 3s',
'margin-top': '0'
});
});
})
});
Fiddle: https://jsfiddle.net/yckszc16/
Since the element is absolute positioned, I took it that it does not need to be nested within the div. Therefore I changed the HTML from this:
<div class="left-zone ads">
<div class="element"></div>
</div>
to this:
<div class="left-zone ads"></div>
<div class="element"></div>
I then adjusted the css to position the element in the same place as it was, like so:
.element{
position: absolute;
top: 60px;
left: -71px;
width: 20px;
height: 30px;
background: green;
}
This allows the object to be manipulated completely seperately to the parent, which also makes it much more flexible in what you can do with it.
To get the animation going on it, I had to change a few bits of code.
Where you were setting the top variable, I removed the + 'px' at the end to allow for the setting of different values in each animation. This is required because the element must have it's top value (60px in this case) added to the animation to keep it in the correct position. I then copied the code that sets the animation going and repeated it for the 'element' div, and added the 60px to it. if that doesn't make sense then check out the code below.
$(document).on('scroll', function() {
top = $(window).scrollTop() < offset ? '0' : $(window).scrollTop() - offset;
$('.ads').css({
'top': top + 'px'
});
$('.element').css({
'top': top + 60 + 'px'
});
})
Next is to get the delay. I first tried the jquery .delay function but it didn't seem to work so I made an even simpler change, add the transition line from your 'ads' div to the 'element' div's css and change the duration to half a second longer. This achieves the required effect of it coming in afterwards! Here is the code:
.element{
position: absolute;
top: 60px;
left: -71px;
width: 20px;
height: 30px;
background: green;
transition: top 1.3s;
}
Here is a jsfiddle if you want to see it in action: https://jsfiddle.net/hdn1oyjd/
Let me know if you have any questions!
Say my image (panoramic) is 10,000 pixels long, but I want to be able to view only 1000 pixels wide at a time, and in order to view more of it, I can just hover my mouse either left or right, and then the image will move accordingly please? If possible, a simple script on HTML? (I'm not sure how to use Javascript or CSS, but if it needs to come down to that, do guide me step by step?
Thank you.
Here is a simple JQuery which you can use to scroll the whole page when hovering over either the left or right;
Example - https://jsfiddle.net/38da9pca/
Need any help implementing it just leave a comment and I will try and help you.
$(function() {
$('#right').on('mouseenter', rscroll);
$('#left').on('mouseenter', lscroll);
$('#right, #left').on('mouseleave', function() {
$('body').stop();
});
function rscroll() {
$('body').animate({
scrollLeft: '+=25'
}, 10, rscroll);
}
function lscroll() {
$('body').animate({
scrollLeft: '-=25'
}, 10, lscroll);
}
});
Edit (Scroll Image Only)
Example - https://jsfiddle.net/38da9pca/1/
I have change it so the lscroll and the rscroll will effect the id of image instead of the body and change it from scrollLeft to left, and that way it will move the images scroll. Dont forgot to change the $('body').stop(); to $('#bg').stop(); or it will never stop scrolling
$(function() {
$('#right').on('mouseenter', rscroll);
$('#left').on('mouseenter', lscroll);
$('#right, #left').on('mouseleave', function() {
$('#bg').stop();
});
function rscroll() {
$('#bg').animate({
left: '-=25'
}, 10, rscroll);
}
function lscroll() {
$('#bg').animate({
left: '+=25'
}, 10, lscroll);
}
});
Here's one. It's using jquery (javascript library). You would have to add it in order to make it work.
Example:
http://www.gayadesign.com/scripts/jqueryphotonav/
Tutorial:
https://blog.gaya.ninja/articles/jquery-convertion-panoramic-photoviewer-in-javascript/
Something like this?
var imgView = document.querySelectorAll('.img-view')[0];
var img = imgView.querySelectorAll('img')[0];
imgView.onmousemove = function(e) {
var x = e.clientX;
var px = ((x / this.offsetWidth) * 100);
var ix = ((px / 100) * img.offsetWidth);
img.style.left = '-' + (ix - this.offsetWidth) + 'px';
}
.img-view {
width: 256px;
height: 160px;
overflow: hidden;
position: relative;
border: 1px solid #ddd;
}
.img-view img {
height: 100%;
position: relative;
top: 0;
left: 0;
}
<div class="img-view">
<img src="http://panoramalove.com/wp-content/uploads/2013/01/nighttime-panoramic-view-of-hong-kong-island-from-the-avenue-of-stars-in-tsim-sha-tsui.jpg">
</div>