Problem synchronizing DOM manipulation with style changes in carousel - javascript

I built a basic picture carousel a while back, and I'm finally getting around to transferring it from MooTools over to jQuery so I can drop MooTools. I've got the script completely functional, but for whatever reason when the carousel slides in one direction, you can see a "pop" where it resets itself.
I've tried playing around with the order it handles everything, but no matter what it seems to always desync for just a fraction of a section.
Here's a copy of my code: https://jsfiddle.net/Chaosxmk/pf6dzchm/
The offending section of code is this:
styles['position'] = 'absolute';
styles[self.params.axis] = -32768;
$(self.list[0]).css(styles).hide();
$(self.list[0]).appendTo(self.carousel);
$(self.list[conf.mi]).css(self.params.axis, (100-conf.pr)+'%');
styles = {};
styles['position'] = 'relative';
styles[self.params.axis] = 'auto';
$(self.list[conf.mi]).css(styles);

Issue is that $.fadeOut() sets display:none on the element, which causes some strange rendering issues in your setTimeout() callback. Works better if you use $.fadeTo() instead:
if (self.params.direction) {
// Go forward
self.carousel.css(self.params.axis, '-'+conf.pr+'%');
$(self.list[0]).fadeTo(400, 0);
$(self.list[conf.mi]).css(self.params.axis, '100%').fadeTo(400, 1);
} else {
// Go backward
self.carousel.css(self.params.axis, conf.pr+'%');
$(self.list[conf.mi-1]).fadeTo(400, 0);
self.list.last().css(self.params.axis, '-'+conf.pr+'%').fadeTo(400, 1);
}
For simplicity I used a 400ms duration, but you can set this to whatever you need.
JSFiddle

Related

Scrolling stops when middle clicked element is removed from the DOM

I'm waist deep in my own React virtualization implementation and one of the minor issues that has been annoying me is that if I middle click on an item in my list and start scrolling, once that element is removed from the DOM the scrolling halts. My first theory was that the element was gaining focus and that preventing that would solve the issue, but what I've tried hasn't been working and I'm not even sure that's the issue.
How can I prevent this from happening?
See this fiddle for a basic demonstration:
https://jsfiddle.net/v169xkym/2/
And the relevant bit of code that handles virtualization:
$('#container').scroll(function(e) {
$('#container').children().each(function(i) {
if ($('.item:eq(' + i + ')').length > 0) {
if ($('.item:eq(' + i + ')').offset().top < 0) {
$('.item:eq(' + i + ')').remove();
$('#topPadding').height($('#topPadding').height() + 45);
}
}
});
});
Basically, I'm using the standard method of removing the element and upping the padding. In my React implementation this is handled different but here you get a basic functional representation.
you can get around this by not having the disappearing element register mouse events.
this can be done with CSS3 :
div.item {
pointer-events : none;
}
(Not entirely sure why, but my guess is that once the element disappears, the origin of the event is missing, so browsers simply stop doing what they were doing.)
Jsfiddle here
Maybe a bit late to the party. A workaround I am using on a virtual scroller is to detect when there is a scroll event, and when there has been no new events for a time, I consider the scroll is complete.
let scrollTimer = null;
let isScrolling = false;
window.addEventListener('scroll', function() {
clearTimeout(scrollTimer);
isScrolling = true;
scrollTimer = setTimeout(()=>{
isScrolling = false;
},500);
}, false);
I then grab a reference to the element that is hovered at the time isScrolling becomes true (using mouseOver) and prevent this element being unloaded until isScrolling is false. It is a bit of a juggle, but works. I am hoping I can find something simpler as it only seems to be a Chrome problem.
Update: It seems to be a known bug, about to be fixed related to pointer-events: none on something that overlays a virtual scroller (reproduction by someone https://codepen.io/flachware/pen/WNMzKav). I have no idea why my work around above works, but nice to know it wont be needed come Chrome 103. https://bugs.chromium.org/p/chromium/issues/detail?id=1330045&q=chrome%20scroll&can=2&sort=-opened

Creating a sliding image gallery that does not glitch on image change

I have created a sliding image gallery and when the button is pushed it slides the picture across and updates the image attribute for the relevant sections.
However this works perfectly like 50% of the time. The other times there is a second glitch and the images then go in place as expected.
I have attached the javascript methods for the animate method and the array change method. I have looked elsewhere and cannot see anyone else with a similar issue or where I am going wrong, especially when it doesn't happen often.
imageGallery.leftSelect.onclick = function () {
window.setTimeout(imageGallery.rightClick, 250);
imageGallery.animateImages('.image1', '.imageRight');
imageGallery.animateImages('.imageRight', '.imageNoneRight');
imageGallery.animateImages('.imageLeft', '.image1');
imageGallery.animateImages('.imageNoneLeft', '.imageLeft');
};
animateImages: function (classFrom, classTo) {
var classMoving = $(classFrom);
var classGoingTo = $(classTo);
classMoving.animate({
top: classGoingTo.css('top'),
left: classGoingTo.css('left'),
width: classGoingTo.css('width'),
opacity: classGoingTo.css('opacity'),
}, 258, function () {
console.log('Animated');
classMoving.css({"width":'', "opacity":'', "top":'', "left":'', });
});
},
rightClick: function () {
imageGallery.imagesDisplay.push(imageGallery.imagesDisplay.shift());
imageGallery.imageNoneLeft.setAttribute('src', imageGallery.imagesDisplay[2]);
imageGallery.imageLeft.setAttribute('src', imageGallery.imagesDisplay[1]);
imageGallery.imageMain.setAttribute('src', imageGallery.imagesDisplay[0]);
imageGallery.imageRight.setAttribute('src', imageGallery.imagesDisplay[10]);
imageGallery.imageNoneRight.setAttribute('src', imageGallery.imagesDisplay[9]);
},
Can someone assist, I really need this to work?
If there is anything not clear or you need more code let me know.
Thanks,
First things first, the culprit was the setAttribute of all images i.e. whatever you were doing inside the rightClick and leftClick functions were the reasons why you were seeing a glitch. Changing src of an img tag produces the glitch.
But then we cannot simply remove it because your approach relies heavily on this swapping of images.
I had to breakdown and really understand your approach first. The way it worked was that you would animate, for example, image1 (the centered one) to move to the position of imageLeft upon click on the rightCarousel button. On that same click, you had a setTimeout of almost the duration of the animation to call rightClick function. This rightClick function then swaps the images so that image1 can always remain at the center and only images can come and go after animation. This was the problem.
What I had to change was that all image tags i.e. imageNoneLeft, imageLeft, image1, imageRight & imageNoneRight would change each others classes such that their position remains changed after animations.
Also, I had to add another animateImages line inside your leftSelect and rightSelect callbacks to animate the furthest images i.e. imageNoneLeft & imageNoneRight to animate to each other's positions with respect to the click of the buttons.
Take a look at this jsFiddle. It will help you understand a lot better. And let me know if you have any questions.
JavaScript:
var imageGallery={
prefix:'https://dl.dropboxusercontent.com/u/45891870/Experiments/StackOverflow/1.5/',
imagesDisplay:['JS.jpg','PIXI.jpg','GSAP.jpg','JS.jpg','PIXI.jpg','GSAP.jpg','JS.jpg','PIXI.jpg','GSAP.jpg','JS.jpg','PIXI.jpg'],
rightSelect:document.querySelector('.rightCarousel'),
leftSelect:document.querySelector('.leftCarousel'),
imageMain:document.querySelector('.image1'),
imageLeft:document.querySelector('.imageLeft'),
imageRight:document.querySelector('.imageRight'),
imageNoneLeft:document.querySelector('.imageNoneLeft'),
imageNoneRight:document.querySelector('.imageNoneRight'),
init:function(){
imageGallery.imagesDisplay.push(imageGallery.imagesDisplay.shift());
imageGallery.imageNoneLeft.setAttribute('src',imageGallery.prefix+imageGallery.imagesDisplay[2]);
imageGallery.imageLeft.setAttribute('src',imageGallery.prefix+imageGallery.imagesDisplay[1]);
imageGallery.imageMain.setAttribute('src',imageGallery.prefix+imageGallery.imagesDisplay[0]);
imageGallery.imageRight.setAttribute('src',imageGallery.prefix+imageGallery.imagesDisplay[10]);
imageGallery.imageNoneRight.setAttribute('src',imageGallery.prefix+imageGallery.imagesDisplay[9]);
},
animateImages:function(classFrom,classTo){
var classMoving=$(classFrom);
var classGoingTo=$(classTo);
classMoving.animate({
top:classGoingTo.css('top'),
left:classGoingTo.css('left'),
width:classGoingTo.css('width'),
opacity:classGoingTo.css('opacity')
},258,function(){
$(this).removeClass(classFrom.substr(1));
$(this).addClass(classTo.substr(1));
$(this).removeAttr('style');
});
}
};
imageGallery.init();
imageGallery.leftSelect.onclick=function(){
imageGallery.animateImages('.imageNoneRight','.imageNoneLeft');
imageGallery.animateImages('.imageRight','.imageNoneRight');
imageGallery.animateImages('.image1','.imageRight');
imageGallery.animateImages('.imageLeft','.image1');
imageGallery.animateImages('.imageNoneLeft','.imageLeft');
};
imageGallery.rightSelect.onclick=function(){
imageGallery.animateImages('.imageNoneLeft','.imageNoneRight');
imageGallery.animateImages('.imageLeft','.imageNoneLeft');
imageGallery.animateImages('.image1','.imageLeft');
imageGallery.animateImages('.imageRight','.image1');
imageGallery.animateImages('.imageNoneRight','.imageRight');
};

jQuery image crossfade with pre-loader

I want a simple image crossfade, similar to http://malsup.com/jquery/cycle/, but with a pre-loader. Is there a good jQuery plugin that does both? Also, I'm not looking for a load bar.
This question is close, but not the same => jQuery Crossfade Plugin
It would be great if it was a solution that defaulted to CSS3, but would otherwise fall back to JS to keep the processing native as possible.
Looking for something that..
will autoplay
without controls
will go to the next image based on time setting, ie. 5 seconds, unless the next image isn't loaded in which case it finishes loading the image and then displays it.
crossfade transition, not fade to black or white, but cross-fade. from the start it would fadein.
no thumbnails or galleries, etc. just the image
If images could be CSS background images, that would be best, so users can't drag out the image simply
Each panel needs to be clickable so a user could click the image and go to a part of the website.
Well, here's my poke at it. The preloader is in vanilla js and the slideshow loop is in jQuery. It's very simple to implement and the concept is even simpler.
Demo
a very simple Demo that illustrates the DOM manipulation approach
HTML
<!-- not much here... just a container -->
<div id="content"></div>
CSS
/* just the important stuff here. The demo has example styling. */
#content
{
position:relative;
}
#content img
{
position:absolute;
}
javascript/jQuery
// simple array
var images = [
"http://imaging.nikon.com/lineup/dslr/d90/img/sample/pic_003t.jpg",
"http://imaging.nikon.com/lineup/dslr/d90/img/sample/pic_005t.jpg",
"http://imaging.nikon.com/lineup/dslr/d90/img/sample/pic_001t.jpg"
];
// some adjustable variables
var delay = 2000;
var transition = 1000;
// the preloader
for(var i in images)
{
var img = document.createElement("img");
img.src = images[i];
img.onload = function(){
var parent = document.getElementById("content");
parent.insertBefore(this,parent.childNodes[0]);
if(i == images.length - 1)
{
i = 0;
startSlides();
}
}
}
// and the actual loop
function startSlides()
{
$("#content img:last").delay(delay).fadeTo(transition,0,function(){
$(this).insertBefore($(this).siblings(":first")).fadeTo(0,1);
startSlides();
});
}
The concept in brief is to fade the first image in a container, once complete change it's position in the DOM (effectively hiding it behind equal tree level siblings), and call the function again. The reason why this works is because it only fades the first child of the container, but on callback it changes what node that is constantly looping the nodes. This makes for a very small source file that is quite effective.
EDIT 1:
and 32 minutes tweaking later...
Demo 2
EDIT 2:
My oh so simple script is now very complicated :P I added in some scaling features that only work on modern browsers but are there if needed. This one also has a loading bar as it preloads the images (may or may not be desirable :P)
small images demo
large images demo
I think you can still do this with the jQuery cycle plugin; other than image preloading, even the jQuery cycle lite version does everything you want by default out-of-the-box.
And if you look here, you'll see that it's pretty simple to add a little Javascript that will add images (after the first two) as they load. You would need to modify the code a little (instead of stack.push(this), you'd want something like stack.push("<div style="background-image:url("+img.src+")"></div>"), for example) but I think it's totally doable.
Edit: here's a link to a SO question about how to make a div into a clickable link.
Edit 2: I liked Joseph's idea to just move the elements to a hidden DIV, so I updated my code a bit. It now also preserves the links each div points to as well: http://jsfiddle.net/g4Hmh/9/
Edit 3: Last update! http://jsfiddle.net/g4Hmh/12/
UPDATE Added the ability to load everything asynchronously.
A wrapper for the jQuery cycle plugin should suffice. You really just need something that monitors if the images loaded and then calls $(elem).cycle(/* options */). Here's my take:
$.fn.cycleWhenLoaded = function(options) {
var target = this,
images = options.images,
loaded = 0,
total = 0,
i;
if(images) {
for(i = 0; i < images.length; i ++) {
$('<img/>').attr('src', images[i]).appendTo(target);
}
}
this.find('> img').each(function(index) {
var img = new Image(),
source = this;
total ++;
if(index > 1)
$(this).hide();
img.onload = function() {
loaded ++;
if(loaded == total) {
target.trigger('preloadcomplete');
target.cycle(options);
}
};
setTimeout(function() {img.src = source.src}, 1);
});
return this;
};
This allows you to either do a simple delay load:
$('.slideshow').cycleWhenLoaded({
fx: 'fade'
});
Or you can do something more complicated and load your images in the script and capture the preload complete event:
$('.slideshow2').hide().cycleWhenLoaded({
fx: 'fade',
images: [
"http://cloud.github.com/downloads/malsup/cycle/beach1.jpg",
"http://cloud.github.com/downloads/malsup/cycle/beach2.jpg",
"http://cloud.github.com/downloads/malsup/cycle/beach3.jpg",
"http://cloud.github.com/downloads/malsup/cycle/beach4.jpg",
"http://cloud.github.com/downloads/malsup/cycle/beach5.jpg"
]
}).bind('preloadcomplete', function() { $(this).show(); });
You can see it in action here: http://fiddle.jshell.net/vmAEW/1/
I don't know how close this is to what you are looking for, but I figured since no one else did I would at least try to help. http://galleria.aino.se/
It at least has a preloader and a fade transition.

Why does setInterval seem to have scope issues in my jQuery plugin?

I'm sorry this isn't more specific, but I'm having trouble isolating the issue.
I've written a very simple jQuery plugin that scrolls images or other elements through a div, like a carousel, on a set interval. I wanted this plugin to work with multiple instances on one page, but when I call it on multiple elements, only the last initialized element scrolls. I assume the way I'm using setInterval is the cause, but I don't understand why.
The function for scrolling is as follows, and the full source is linked above.
function scrollRight() {
// Don't animate if the mouse is over the scrollah
if (hovering) { return; }
/* If we're at the end, flip back to the first image
* before animating, lest we view blankness in the wrapper
*/
if (position === nChildren) {
position = 0;
$wrapper.css('left', '0px');
}
// Animate to the next view
position++;
$wrapper.animate({
left: position*-width+'px'
}, 1000, 'swing', function() {
// Animation complete.
});
}
setInterval(scrollRight, 5000);
So why do individual instances of this plugin not scroll once more have been initialized?
I think if you change $wrapper = $this.find('.wrapper'); to var $wrapper = $this.find('.wrapper'); it might work.
Learned this the other day from Stack Overflow: variables that don't use the var keyword are implicitly global in scope, so I think each scroller is overwriting the same global $wrapper variable.
EDIT: might also want to do var $this = $(this);.

Fx on JavaScript: How to fade stuff, etc without frameworks?

I would like to learn how to do fade, and similar effects on JavaScript. I often get answers, like why not use jQuery, Mootools, etc ? Well, I want to learn how stuff works, then I won't mind using any of these frameworks.
I'm currently learning about making changes on the DOM, so, I've read a lot of stuff on this theme. Also, I've read about Reflow, still, I didn't find any cool stuff on Repaint, but, I'll keep searching.
From seeing source files etc, I see a few methods, that I don't know if they've created or are Core methods of JS.
My question is, is there any resource where I can learn all this neat stuff like smooth position change, fading elements trough opacity or whatever, etc?
Take a look at emile.js. It's brand-spanking new. Great way to learn how to do your own.
Introduced at the recent jsconf conference. Written by Thomas Fuchs (script.aculo.us).
http://github.com/madrobby/emile
Émile Stand-alone CSS animation
JavaScript mini-framework
Doesn't need
a JavaScript framework
Full set of CSS
properties for animation (length-based
and colors)
Easing and callbacks
Less
than 50 lines of code
Get updates on
Twitter: http://twitter.com/emilejs
here's an example that works in firefox and chrome. ie doesn't respect the opacity style.
var ELEMENT;
var STEPS;
var INTERVAL;
var COUNT;
var TIMERID;
// 5 * 200ms = 1 second
STEPS = 5;
INTERVAL = 200;
function Button1_onclick() {
ELEMENT = document.getElementById("foo");
COUNT = STEPS - 1;
TIMERID = setInterval(Fade, INTERVAL);
}
function Fade() {
ELEMENT.style.opacity = String(COUNT / STEPS);
COUNT--;
if (COUNT < 0) {
clearInterval(TIMERID);
TIMERID = 0;
}
}
setInterval and clearInterval are standard js functions. they will execute the given function every x milliseconds. in our case we kill it when we've hit 0 opacity.
sliding a window is a similar process. you'd set the left/right/top/bottom style instead of opacity.
Fading using javascript is basically modifying opacity of an element programmatically. The "smoothness" is done by using timers. Same for position/size changes. You need to read up on css to understand what style properties of an element you have to control using javascript for the effect you want.
If you are really curious, you can dive into the source of yui in github: http://github.com/yui

Categories

Resources