Fading an element onfocus and onblur - javascript

I have a single word counter on the page that counts the number of characters for each input element.
When an input element is focused, a fadeIn function is triggered to fade in the counter element. When the input element loses focus i.e. is blurred, a fadeOut function is triggered on the counter.
However when you focus and blur the elements several times and in fast intervals with either the mouse or the keyboard, the fade effect is noticed less and less. The counter which fades in and out ends up only blinking between opacity: 0 and 1. A page refresh is required to achieve the fade effect again, until it screws up again.
This problem is also triggered if you tab through all elements and regain your initial focus on the browser address bar.
Why does this happen? Could it have something to do with the fade function and setInterval?
Here's an example fade function I use from http://www.scriptiny.com :
var fadeEffect=function(){
return{
init:function(id, flag, target){
this.elem = document.getElementById(id);
clearInterval(this.elem.si);
this.target = target ? target : flag ? 100 : 0;
this.flag = flag || -1;
this.alpha = this.elem.style.opacity ? parseFloat(this.elem.style.opacity) * 100 : 0;
this.si = setInterval(function(){fadeEffect.tween()}, 20);
},
tween:function(){
if(this.alpha == this.target){
clearInterval(this.elem.si);
}else{
var value = Math.round(this.alpha + ((this.target - this.alpha) * .05)) + (1 * this.flag);
this.elem.style.opacity = value / 100;
this.elem.style.filter = 'alpha(opacity=' + value + ')';
this.alpha = value
}
}
}
}();

Perhaps it's because you're not doing clearInterval() properly. You are setting the interval like this:
this.si = setInterval();
but clearing the interval like this:
clearInterval(this.elem.si)
Shouldn't they both be this.si or both be this.elem.si?
Also, how are you invoking fadeEffect? Are you using new fadeEffect()? Are you sure that this is being handled properly when you call methods?

Using jquery probably you are looking for such behaviour:
$(document).ready(function () {
$('#test').fadeTo('fast', 0.5); // to set default opacity. CSS can be used here
$('#test').focus(function () {
$(this).fadeTo('fast', 1);
}).blur(function () {
$(this).fadeTo('fast', 0.5);
});
});
here is live example: http://jsfiddle.net/hrzw7/1/
if you have a lot of such controls, use .class selector: http://jsfiddle.net/hrzw7/3/

Related

Photoswipe 4.0: Initiate 'swipe to next' programatically

SITUATION
I have been trying to trigger the 'slide to next picture' animation when the NEXT button is clicked, but i have not found a solution for this.
There is an ongoing discussion about this on GitHub, but it is only about adding the option for a slide animation, not about how to actually do it with PS as it is right now.
There was an option for it in 3.0 but as 4.0 is a complete rewrite it does not work anymore.
QUESTION
Instead of just 'jumping' to the next/prev picture when an arrow is clicked, i need the 'slide transition' that is also used when swiping/dragging the image.
There is no option to trigger that, so how can i manually trigger this effect with JS?
PhotoSwipe Slide Transitions
So, I added slide transitions to Photoswipe, and it's working nicely without disturbing native behavior.
http://codepen.io/mjau-mjau/full/XbqBbp/
http://codepen.io/mjau-mjau/pen/XbqBbp
The only limitation is that transition will not be applied between seams in loop mode (for example when looping from last slide to slide 1). In examples I have used jQuery.
Essentially, it works by simply adding a CSS transition class to the .pswp__container on demand, but we need to add some javascript events to prevent the transition from interfering with Swipe, and only if mouseUsed. We also add a patch so the transition does not get added between loop seams.
1. Add the below to your CSS
It will be applied on-demand from javascript when required.
.pswp__container_transition {
-webkit-transition: -webkit-transform 333ms cubic-bezier(0.4, 0, 0.22, 1);
transition: transform 333ms cubic-bezier(0.4, 0, 0.22, 1);
}
2. Add javascript events to assist in assigning the transition class
This can go anywhere, but must be triggered after jQuery is loaded.
var mouseUsed = false;
$('body').on('mousedown', '.pswp__scroll-wrap', function(event) {
// On mousedown, temporarily remove the transition class in preparation for swipe. $(this).children('.pswp__container_transition').removeClass('pswp__container_transition');
}).on('mousedown', '.pswp__button--arrow--left, .pswp__button--arrow--right', function(event) {
// Exlude navigation arrows from the above event.
event.stopPropagation();
}).on('mousemove.detect', function(event) {
// Detect mouseUsed before as early as possible to feed PhotoSwipe
mouseUsed = true;
$('body').off('mousemove.detect');
});
3. Add beforeChange listener to re-assign transition class on photoswipe init
The below needs to be added in your PhotoSwipe init logic.
// Create your photoswipe gallery element as usual
gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, options);
// Transition Manager function (triggers only on mouseUsed)
function transitionManager() {
// Create var to store slide index
var currentSlide = options.index;
// Listen for photoswipe change event to re-apply transition class
gallery.listen('beforeChange', function() {
// Only apply transition class if difference between last and next slide is < 2
// If difference > 1, it means we are at the loop seam.
var transition = Math.abs(gallery.getCurrentIndex()-currentSlide) < 2;
// Apply transition class depending on above
$('.pswp__container').toggleClass('pswp__container_transition', transition);
// Update currentSlide
currentSlide = gallery.getCurrentIndex();
});
}
// Only apply transition manager functionality if mouse
if(mouseUsed) {
transitionManager();
} else {
gallery.listen('mouseUsed', function(){
mouseUsed = true;
transitionManager();
});
}
// init your gallery per usual
gallery.init();
You can just use a css transition:
.pswp__container{
transition:.3s ease-in-out all;
}
This might not be ideal for performance on mobile, but I just add this transition in a media query and allow users to use the swipe functionality on smaller screens.
I finally bit the bullet and spent some time making this work as nobody seemed to have a solution for that, not here neither on GitHub or anywhere else.
SOLUTION
I used the fact that a click on the arrow jumps to the next item and triggers the loading of the next image and sets the whole slide state to represent the correct situation in an instant.
So i just added custom buttons which would initiate a slide transition and then triggered a click on the original buttons (which i hid via CSS) which would update the slide state to represent the situation i created visually.
Added NEW next and prev arrows
Hid the ORIGINAL next and prev arrows via css
Animated the slide myself when the NEW next or prev arrows were clicked
Then triggered the click on the ORIGINAL next or prev arrows programmatically
So here is the code:
HTML
// THE NEW BUTTONS
<button class="NEW-button-left" title="Previous (arrow left)">PREV</button>
<button class="NEW-button-right" title="Next (arrow right)">NEXT</button>
// added right before this original lines of the example code
<button class="pswp__button pswp__button--arrow--left ...
CSS
pswp__button--arrow--left,
pswp__button--arrow--right {
display: none;
}
NEW-button-left,
NEW-button-right {
/* whatever you fancy */
}
JAVASCRIPT (helper functions)
var tx = 0; // current translation
var tdir = 0;
var slidepseactive = false;
// helper function to get current translate3d positions
// as per https://stackoverflow.com/a/7982594/826194
function getTransform(el) {
var results = $(el).css('-webkit-transform').match(/matrix(?:(3d)\(-{0,1}\d+(?:, -{0,1}\d+)*(?:, (-{0,1}\d+))(?:, (-{0,1}\d+))(?:, (-{0,1}\d+)), -{0,1}\d+\)|\(-{0,1}\d+(?:, -{0,1}\d+)*(?:, (-{0,1}\d+))(?:, (-{0,1}\d+))\))/)
if(!results) return [0, 0, 0];
if(results[1] == '3d') return results.slice(2,5);
results.push(0);
return results.slice(5, 8);
}
// set the translate x position of an element
function translate3dX($e, x) {
$e.css({
// TODO: depending on the browser we need one of those, for now just chrome
//'-webkit-transform': 'translate3d(' +String(x) + 'px, 0px, 0px)'
//, '-moz-transform': 'translate3d(' +String(x) + 'px, 0px, 0px)'
'transform': 'translate3d(' +String(x) + 'px, 0px, 0px)'
});
};
JAVASCRIPT (main)
// will slide to the left or to the right
function slidePS(direction) {
if (slidepseactive) // prevent interruptions
return;
tdir = -1;
if (direction == "left") {
tdir = 1;
}
// get the current slides transition position
var t = getTransform(".pswp__container");
tx = parseInt(t[0]);
// reset anim counter (you can use any property as anim counter)
$(".pswp__container").css("text-indent", "0px");
slidepseactive = true;
$(".pswp__container").animate(
{textIndent: 100},{
step: function (now, fx) {
// here 8.7 is the no. of pixels we move per animation step %
// so in this case we slide a total of 870px, depends on your setup
// you might want to use a percentage value, in this case it was
// a popup thats why it is a a fixed value per step
translate3dX($(this), tx + Math.round(8.7 * now * tdir));
},
duration: '300ms',
done: function () {
// now that we finished sliding trigger the original buttons so
// that the photoswipe state reflects the new situation
slidepseactive = false;
if (tdir == -1)
$(".pswp__button--arrow--right").trigger("click");
else
$(".pswp__button--arrow--left").trigger("click");
}
},
'linear');
}
// now activate our buttons
$(function(){
$(".NEW-button-left").click(function(){
slidePS("left");
});
$(".NEW-button-right").click(function(){
slidePS("right");
});
});
I used info from those SE answers:
jQuery animate a -webkit-transform
Get translate3d values of a div?
The PhotoSwipe can do this by itself when you use swipe gesture. So why not to use the internal code instead of something that doesn't work well?
With my solution everything works well, the arrow clicks, the cursor keys and even the loop back at the end and it doesn't break anything.
Simply edit the photoswipe.js file and replace the goTo function with this code:
goTo: function(index) {
var itemsDiff;
if (index == _currentItemIndex + 1) { //Next
itemsDiff = 1;
}
else { //Prev
itemsDiff = -1;
}
var itemChanged;
if(!_mainScrollAnimating) {
_currZoomedItemIndex = _currentItemIndex;
}
var nextCircle;
_currentItemIndex += itemsDiff;
if(_currentItemIndex < 0) {
_currentItemIndex = _options.loop ? _getNumItems()-1 : 0;
nextCircle = true;
} else if(_currentItemIndex >= _getNumItems()) {
_currentItemIndex = _options.loop ? 0 : _getNumItems()-1;
nextCircle = true;
}
if(!nextCircle || _options.loop) {
_indexDiff += itemsDiff;
_currPositionIndex -= itemsDiff;
itemChanged = true;
}
var animateToX = _slideSize.x * _currPositionIndex;
var animateToDist = Math.abs( animateToX - _mainScrollPos.x );
var finishAnimDuration = 333;
if(_currZoomedItemIndex === _currentItemIndex) {
itemChanged = false;
}
_mainScrollAnimating = true;
_shout('mainScrollAnimStart');
_animateProp('mainScroll', _mainScrollPos.x, animateToX, finishAnimDuration, framework.easing.cubic.out,
_moveMainScroll,
function() {
_stopAllAnimations();
_mainScrollAnimating = false;
_currZoomedItemIndex = -1;
if(itemChanged || _currZoomedItemIndex !== _currentItemIndex) {
self.updateCurrItem();
}
_shout('mainScrollAnimComplete');
}
);
if(itemChanged) {
self.updateCurrItem(true);
}
return itemChanged;
},

Can't Call a Function

JFiddler - http://jsfiddle.net/x2Uwg/
So I have a fade function which first fades the image in and then fades it out. I'd like the image to fade in, change and then fade out. The problem is the only way I can get the img to fade out is if I have it in a button like this.
<button class="hide2" onclick="fadeEffect.init('chestImg', 0)">Fade Out</div>
I'd like for it to be something like this
Img is show
User clicks one button
Image fades in, then changes.
Image fades out.
Right now it is like this.
Img is show
User clicks one button
Image fades in, then changes.
User clicks another button
Image fades out.
My fade function looks like so.
var fadeEffect=function(){
return{
init:function(id, flag, target){
this.elem = document.getElementById(id);
clearInterval(this.elem.si);
this.target = target ? target : flag ? 100 : 0;
this.flag = flag || -1;
this.alpha = this.elem.style.opacity ? parseFloat(this.elem.style.opacity) * 100 : 0;
this.elem.si = setInterval(function(){fadeEffect.tween()}, 20);
},
tween:function(){
if(this.alpha == this.target){
clearInterval(this.elem.si);
}else{
var value = Math.round(this.alpha + ((this.target - this.alpha) * .05)) + (1 * this.flag);
this.elem.style.opacity = value / 100;
this.elem.style.filter = 'alpha(opacity=' + value + ')';
this.alpha = value
}
}
}
}();
I call the fade in like so
fadeEffect.init('chestImg', 1);
And the fade out like so
fadeEffect.init('chestImg', 0);
But if I place them in the same function it will not work. Any help?
When you call both fadeEffect.init('chestImg', 1); and fadeEffect.init('chestImg', 0); in the same function, then both effects are running simultaneously, which I think is leading to your problem. I think that what you need to do is:
fadeEffect.init('chestImg', 1);
setTimeout(function(){fadeEffect.init('chestImg', 0);},20);
I believe your problem is with binding this. When a function is called from within the clickhandler of a button, this is set to that button node. You can manually set the this object by using the call function
var button = document.getElementById('hide-id');
fadeEffect.init.call(button,'chestImg', 0);
Why write ".call(this)" at the end of an javascript anonymous function?

Javascript fade in doesn't visibly animate

So I've created the following function to fade elements in and passed in a div that I want to fade in which in this case is an image gallery popup that I want to show when a user clicks an image thumbnail on my site. I'm also passing in a speed value (iSpeed) which the timeout uses for it's time value. In this case I'm using 25 (25ms).
I've stepped through this function whilst doing so it appears to be functioning as expected. If the current opacity is less than 1, then it is incremented and it will recall itself after the timeout until the opacity reaches 1. When it reaches one it stops fading and returns.
So after stepping through it, I take off my breakpoints and try to see it in action but for some reason my gallery instantly appears without any sense of fading.
var Effects = new function () {
this.Fading = false;
this.FadeIn = function (oElement, iSpeed) {
//set opacity to zero if we haven't started fading yet.
if (this.Fading == false) {
oElement.style.opacity = 0;
}
//if we've reached or passed max opacity, stop fading
if (oElement.style.opacity >= 1) {
oElement.style.opacity = 1;
this.Fading = false;
return;
}
//otherwise, fade
else {
this.Fading = true;
var iCurrentOpacity = parseFloat(oElement.style.opacity);
oElement.style.opacity = iCurrentOpacity + 0.1;
setTimeout(Effects.FadeIn(oElement, iSpeed), iSpeed);
}
}
}
Here's where I'm setting up the gallery.
this.Show = function (sPage, iImagesToDisplay, oSelectedImage) {
//create and show overlay
var oOverlay = document.createElement('div');
oOverlay.id = 'divOverlay';
document.body.appendChild(oOverlay);
//create and show gallery box
var oGallery = document.createElement('div');
oGallery.id = 'divGallery';
oGallery.style.opacity = 0;
document.body.appendChild(oGallery);
//set position of gallery box
oGallery.style.top = (window.innerHeight / 2) - (oGallery.clientHeight / 2) + 'px';
oGallery.style.left = (window.innerWidth / 2) - (oGallery.clientWidth / 2) + 'px';
//call content function
ImageGallery.CreateContent(oGallery, sPage, iImagesToDisplay, oSelectedImage);
//fade in gallery
Effects.FadeIn(oGallery, 25);
}
Could anyone help me out?
Also, I'm using IE10 and I've also tried Chrome, same result.
Thanks,
Andy
This line:
setTimeout(Effects.FadeIn(oElement, iSpeed), iSpeed);
calls Effects.FadeIn with the given arguments, and feeds its return value into setTimeout. This is exactly like foo(bar()), which calls bar immediately, and then feeds its return value into foo.
Since your FadeIn function doesn't return a function, that would be the problem.
Perhaps you meant:
setTimeout(function() {
Effects.FadeIn(oElement, iSpeed);
}, iSpeed);
...although you'd be better off creating that function once and reusing it.
For instance, I think this does what you're looking for, but without recreating functions on each loop:
var Effects = new function () {
this.FadeIn = function (oElement, iSpeed) {
var fading = false;
var timer = setInterval(function() {
//set opacity to zero if we haven't started fading yet.
if (fading == false) { // Consider `if (!this.Fading)`
oElement.style.opacity = 0;
}
//if we've reached or passed max opacity, stop fading
if (oElement.style.opacity >= 1) {
oElement.style.opacity = 1;
clearInterval(timer);
}
//otherwise, fade
else {
fading = true;
var iCurrentOpacity = parseFloat(oElement.style.opacity);
oElement.style.opacity = iCurrentOpacity + 0.1;
}
}, iSpeed);
};
};
Your code has a lot of problems. The one culpable for the element appearing immediately is that you call setTimeout not with a function but with the result of a function, because Effects.FadeIn will be executed immediately.
setTimeout(function(){Effects.FadeIn(oElement, iSpeed)}, iSpeed);
will probably act as you intend.
But seriously, you probably should not re-invent this wheel. jQuery will allow you to fade elements in and out easily and CSS transitions allow you to achieve element fading with as much as adding or removing a CSS class.
T.J. and MoMolog are both right about the bug: you're invoking the Effects.FadeIn function immediately before passing the result to setTimeout—which means that Effects.FadeIn calls itself synchronously again and again until the condition oElement.style.opacity >= 1 is reached.
As you may or may not know, many UI updates that all take place within one turn of the event loop will be batched together on the next repaint (or something like that) so you won't see any sort of transition.
This jsFiddle includes the suggested JS solution, as well as an alternate approach that I think you may find to be better: simply adding a CSS class with the transition property. This will result in a smoother animation. Note that if you go this route, though, you may need to also include some vendor prefixes.

Synchronized scrolling using jQuery?

I am trying to implement synchronized scrolling for two DIV with the following code.
DEMO
$(document).ready(function() {
$("#div1").scroll(function () {
$("#div2").scrollTop($("#div1").scrollTop());
});
$("#div2").scroll(function () {
$("#div1").scrollTop($("#div2").scrollTop());
});
});
#div1 and #div2 is having the very same content but different sizes, say
#div1 {
height : 800px;
width: 600px;
}
#div1 {
height : 400px;
width: 200px;
}
With this code, I am facing two issues.
1) Scrolling is not well synchronized, since the divs are of different sizes. I know, this is because, I am directly setting the scrollTop value. I need to find the percentage of scrolled content and calculate corresponding scrollTop value for the other div. I am not sure, how to find the actual height and current scroll position.
2) This issue is only found in firefox. In firefox, scrolling is not smooth as in other browsers. I think this because the above code is creating a infinite loop of scroll events.
I am not sure, why this is only happening with firefox. Is there any way to find the source of scroll event, so that I can resolve this issue.
Any help would be greatly appreciated.
You can use element.scrollTop / (element.scrollHeight - element.offsetHeight) to get the percentage (it'll be a value between 0 and 1). So you can multiply the other element's (.scrollHeight - .offsetHeight) by this value for proportional scrolling.
To avoid triggering the listeners in a loop you could temporarily unbind the listener, set the scrollTop and rebind again.
var $divs = $('#div1, #div2');
var sync = function(e){
var $other = $divs.not(this).off('scroll'), other = $other.get(0);
var percentage = this.scrollTop / (this.scrollHeight - this.offsetHeight);
other.scrollTop = percentage * (other.scrollHeight - other.offsetHeight);
// Firefox workaround. Rebinding without delay isn't enough.
setTimeout( function(){ $other.on('scroll', sync ); },10);
}
$divs.on( 'scroll', sync);
http://jsfiddle.net/b75KZ/5/
Runs like clockwork (see DEMO)
$(document).ready(function(){
var master = "div1"; // this is id div
var slave = "div2"; // this is other id div
var master_tmp;
var slave_tmp;
var timer;
var sync = function ()
{
if($(this).attr('id') == slave)
{
master_tmp = master;
slave_tmp = slave;
master = slave;
slave = master_tmp;
}
$("#" + slave).unbind("scroll");
var percentage = this.scrollTop / (this.scrollHeight - this.offsetHeight);
var x = percentage * ($("#" + slave).get(0).scrollHeight - $("#" + slave).get(0).offsetHeight);
$("#" + slave).scrollTop(x);
if(typeof(timer) !== 'undefind')
clearTimeout(timer);
timer = setTimeout(function(){ $("#" + slave).scroll(sync) }, 200)
}
$('#' + master + ', #' + slave).scroll(sync);
});
This is what I'm using. Just call the syncScroll(...) function with the two elements you want to synchronize. I found pawel's solution had issues with continuing to slowly scroll after the mouse or trackpad was actually done with the operation.
See working example here.
// Sync up our elements.
syncScroll($('.scroll-elem-1'), $('.scroll-elem-2'));
/***
* Synchronize Scroll
* Synchronizes the vertical scrolling of two elements.
* The elements can have different content heights.
*
* #param $el1 {Object}
* Native DOM element or jQuery selector.
* First element to sync.
* #param $el2 {Object}
* Native DOM element or jQuery selector.
* Second element to sync.
*/
function syncScroll(el1, el2) {
var $el1 = $(el1);
var $el2 = $(el2);
// Lets us know when a scroll is organic
// or forced from the synced element.
var forcedScroll = false;
// Catch our elements' scroll events and
// syncronize the related element.
$el1.scroll(function() { performScroll($el1, $el2); });
$el2.scroll(function() { performScroll($el2, $el1); });
// Perform the scroll of the synced element
// based on the scrolled element.
function performScroll($scrolled, $toScroll) {
if (forcedScroll) return (forcedScroll = false);
var percent = ($scrolled.scrollTop() /
($scrolled[0].scrollHeight - $scrolled.outerHeight())) * 100;
setScrollTopFromPercent($toScroll, percent);
}
// Scroll to a position in the given
// element based on a percent.
function setScrollTopFromPercent($el, percent) {
var scrollTopPos = (percent / 100) *
($el[0].scrollHeight - $el.outerHeight());
forcedScroll = true;
$el.scrollTop(scrollTopPos);
}
}
If the divs are of equal sizes then this code below is a simple way to scroll them synchronously:
scroll_all_blocks: function(e) {
var scrollLeft = $(e.target)[0].scrollLeft;
var len = $('.scroll_class').length;
for (var i = 0; i < len; i++)
{
$('.scroll_class')[i].scrollLeft = scrollLeft;
}
}
Here im using horizontal scroll, but you can use scrollTop here instead. This function is call on scroll event on the div, so the e will have access to the event object.
Secondly, you can simply have the ratio of corresponding sizes of the divs calculated to apply in this line $('.scroll_class')[i].scrollLeft = scrollLeft;
I solved the sync scrolling loop problem by setting the scroll percentage to fixed-point notation: percent.toFixed(0), with 0 as the parameter. This prevents mismatched fractional scrolling heights between the two synced elements, which are constantly trying to "catch up" with each other. This code will let them catch up after at most a single extra step (i.e., the second element may continue to scroll an extra pixel after the user stops scrolling). Not a perfect solution or the most sophisticated, but certainly the simplest I could find.
var left = document.getElementById('left');
var right = document.getElementById('right');
var el2;
var percentage = function(el) { return (el.scrollTop / (el.scrollHeight - el.offsetHeight)) };
function syncScroll(el1) {
el1.getAttribute('id') === 'left' ? el2 = right : el2 = left;
el2.scrollTo( 0, (percentage(el1) * (el2.scrollHeight - el2.offsetHeight)).toFixed(0) ); // toFixed(0) prevents scrolling feedback loop
}
document.getElementById('left').addEventListener('scroll',function() {
syncScroll(this);
});
document.getElementById('right').addEventListener('scroll',function() {
syncScroll(this);
});
I like pawel's clean solution but it lacks something I need and has a strange scrolling bug where it continues to scroll and my plugin will work on multiple containers not just two.
http://www.xtf.dk/2015/12/jquery-plugin-synchronize-scroll.html
Example & demo: http://trunk.xtf.dk/Project/ScrollSync/
Plugin: http://trunk.xtf.dk/Project/ScrollSync/jquery.scrollSync.js
$('.scrollable').scrollSync();
If you don't want proportional scrolling, but rather to scroll an equal amount of pixels on each field, you could add the value of change to the current value of the field you're binding the scroll-event to.
Let's say that #left is the small field, and #right is the bigger field.
var oldRst = 0;
$('#right').on('scroll', function () {
l = $('#left');
var lst = l.scrollTop();
var rst = $(this).scrollTop();
l.scrollTop(lst+(rst-oldRst)); // <-- like this
oldRst = rst;
});
https://jsfiddle.net/vuvgc0a8/1/
By adding the value of change, and not just setting it equal to #right's scrollTop(), you can scroll up or down in the small field, regardless of its scrollTop() being less than the bigger field. An example of this is a user page on Facebook.
This is what I needed when I came here, so I thought I'd share.
From the pawel solution (first answer).
For the horizzontal synchronized scrolling using jQuery this is the solution:
var $divs = $('#div1, #div2'); //only 2 divs
var sync = function(e){
var $other = $divs.not(this).off('scroll');
var other = $other.get(0);
var percentage = this.scrollLeft / (this.scrollWidth - this.offsetWidth);
other.scrollLeft = percentage * (other.scrollWidth - other.offsetWidth);
setTimeout( function(){ $other.on('scroll', sync ); },10);
}
$divs.on('scroll', sync);
JSFiddle
An other solution for multiple horizontally synchronized divs is this, but it works for divs with same width.
var $divs = $('#div1, #div2, #div3'); //multiple divs
var sync = function (e) {
var me = $(this);
var $other = $divs.not(me).off('scroll');
$divs.not(me).each(function (index) {
$(this).scrollLeft(me.scrollLeft());
});
setTimeout(function () {
$other.on('scroll', sync);
}, 10);
}
$divs.on('scroll', sync);
NB: Only for divs with same width
JSFiddle

Image Rotation using pure Javascript

PLEASE DO NOT RECOMMEND JQUERY - I AM DOING THIS EXERCISE FOR LEARNING PURPOSES.
I have implemented a JavaScript, which rotates images (_elementSlideChange) on a timer, using a set interval of 10 seconds. Also I have added a slide functionality to this, which is 7 milliseconds (_slideImage).
The image rotates automatically every 10 seconds on page load, and I have also provided next and previous buttons, which allow the user to change the images manually.
_elementSlideChange: function () {
var myString;
var myText;
for (var i = 0; i < this._imgArray.length; i++) {
var imageArr = "url(" + this._imgArray[i].src + ")";
var imageBg = this._imageHolder.style.background + "";
if (imageArr == imageBg) {
if (i == (this._imgArray.length - 1)) {
myString = "url(" + this._imgArray[0].src + ")";
myText = this._infoArray[0];
} else {
myString = "url(" + this._imgArray[(i + 1)].src + ")";
myText = this._infoArray[i + 1];
}
}
}
this._imageNextSlide.style.background = myString;
this._imageNextSlide.style.background);
this._infoElement.innerHTML = myText;
this._myTimer = setInterval(MyProject.Utils.createDelegate(this._slideImage, this), 7);
},
_slideImage: function () {
if (parseInt(this._imageHolder.style.width) >= 0 && parseInt(this._imageNextSlide.style.width) <= 450) {
this._imageHolder.style.backgroundPosition = "right";
this._imageHolder.style.width = (parseInt(this._imageHolder.style.width) - 1) + 'px';
console.log(this._imageNextSlide.style.background);
this._imageNextSlide.style.width = (parseInt(this._imageNextSlide.style.width) + 1) + 'px';
} else {
console.log("reached 0px");
if (parseInt(this._imageHolder.style.width) == 0) {
this._imageHolder.style.background = this._imageNextSlide.style.background;
this._imageHolder.style.width = 450 + 'px';
this._imageHolder === this._imageNextSlide;
this._imageHolder.className = "orginalImage";
this._imageNextSlide.style.width = 0 + "px";
this._imageNextSlide = this._dummyImageNextSlide;
this._imagesElement.appendChild(this._imageHolder);
this._imagesElement.appendChild(this._imageNextSlide);
clearInterval(this._myTimer);
}
clearInterval(this._myTimer);
clearInterval(this._elementSlideChange);
}
}
So when the user clicks on the Next arrow button, the event listener for "click" is triggered. This creates a div for the current image on display, and creates a new div, which will contain the next image. The image slide and rotation works correctly (whether it's onLoad or onClick). The issue I have is if I click the Next button, while the new div image is sliding into position, it causes it to run into an infinite loop, so the same div with the image to be displayed keeps sliding in, and the more you click the Next button, the faster the image starts to rotate.
I have tried putting a clear interval for the image rotation and slider, but I do understand my code is wrong, which causes the infinite loop of the sliding image. And I know I am close to finishing the functionality.
Can anyone please advise where I could be going wrong? Or should I try to implement the sliding DIV in another way?
Once again please don't recommend jQuery.
And thank you for your help in advance.
Kush
To solve the issue, I did re-write the entire code, where I had a next and previous button event listener.
myProject.Utils.addHandler(this._nextImageElement, "click", myProject.Utils.createDelegate(this._changeImage, this));
Both the buttons will call the same function :
_changeImage: function (e)
In this function I check to see if the function is Transition (changing images),
I declare a boolean var forward = e.target == this._nextImageElement;
Then check to see the current index if forward ? Add 1 else minus 1
this._currentImageIndex += forward ? 1 : -1;
If its at the end of the Array and forward is true, assign the this._currentImageIndex to reset to 0 or Array.length – 1 if it’s in reverse
Then call another function which gives the ‘div’ a sliding effect. In this case call it this._transitionImage(forward);
In this function, set the this._inTranstion to true. (Because the div’s are sliding in this case).
The following code solved the issue i was having.
this._slideImageElement.style.backgroundImage = "url(\"" + this._imgArray[this._currentImageIndex].src + "\")";
this._slideImageElement.style.backgroundPosition = forward ? "left" : "right";
this._slideImageElement.style.left = forward ? "auto" : "0px";
this._slideImageElement.style.right = forward ? "0px" : "auto";
The above code is very important as the object is to place the “sliding in div” Left or Right of the current Visible “div” to the user, and this is mainly dependent on if the forward variable is true or false.
var i = 0;
Then start the transition by
setInterval( function() {
this._currentImageElement.style.backgroundPosition = (forward ? -1 : 1) * (i + 1) + "px";
this._slideImageElement.style.width = (i + 1) + "px";
Notice the forward will determine if the bgPosition will go to the left if its forward as we multiple by -1 or +1,
So for example
If the user clicks NEXT BUTTON,
Forward = true
So the first thing we do is set the
this._slideImageElement.style.backgroundPosition = "left"
Then
this._slideImageElement.style.left = "auto"
this._slideImageElement.style.right = "0px"
This means when the sliding image moves in its background position is LEFT but the div is placed on the RIGHT to 0px;
then this._currentImageElement.style.backgroundPosition = -1 * (i + 1)
Which moves the position of the currentImageElement to the left by 1px,
Increase the width of the slideImage which in this case is right of the current div,
and as the current div moves to the left the sliding image starts to appear from the right. (By default set the width of slideImageElement to 0px so the div exists but isn’t visible to the user). This gives it the slide effect of moving forward new image coming from the right.
this._slideImageElement.style.width = (i + 1) + "px";
then declare it to stop when it it’s the image width. In this case it will be 500px.
if ((i = i + 2) == 500) {
In this if statement reset the currentImageElement background and the background position “right” or “left” don’t really matter as long it has been reset.
Clear the interval
Set the transition to false again
Then call a setTimeout for the function changeImage, which will continue until the slide is completed.
The following shows the reset code as this is very important to prevent repeating the same image (This solved my entire issue)
// set the current image to the "new" current image and reset it's background position
this._currentImageElement.style.backgroundImage = "url(\"" + this._imgArray[this._currentImageIndex].src + "\")";
this._currentImageElement.style.backgroundPosition = "right";
// reset the slide image width
this._slideImageElement.style.width = "0px";
// clear the transition interval and mark as not in transition
clearInterval(this._transitionInterval);
this._inTransition = false;
// setup the next image timer
this._nextImageTimeout = setTimeout(myProject.Utils.createDelegate(this._changeImage, this), 2500);
}
I have provided a thorough detail because then it easier to understand the logic of the problem, and even if your not having the same issue, this may help you fingure out any problem.
I couldn't provide a JSfiddle, as i have created my CSS using Javascript, there are different ways of doing this, but i wanted to understand the logic behind the forward and reverse, and having a timer which continuously goes forward.
It seems like you want to cancel the animation on the slide (perhaps have it fade out while the next slide animates in, cancel its animation abruptly or let it finish and ignore the button click)
What I usually do, personally, is check for the animated state (yes, I use jquery, but you should be able to test the CSS or positioning values you are using to animate in the same way) you could even add an "active" class or data type during animation to make testing easier. Global flags work, too. If there is animation, ignore the button. (For my work... Depends on your intention)
Like I said, the problem may be with button behaviour not with the animation routine. It would be useful to see how you are calling this from the button click, and what your intended results are going to be.
How about CSS3 transitions?
transition: all 1s ease 0.5s;
Simple example on JS Fiddle.
This takes care of the animation, so you just need to set the intended destination using JavaScript, i.e.
this.style.left = '100px';
Or
this.style.top = '30px';
And CSS3 transitions will smoothly slide the element.
Cross Browser Note!
The transition property may need a vendor prefix for some browsers, I am using the latest production Firefox and you don't need -moz for that. Same goes for Opera, no '-o' required. Internet Exporer 10 needs no prefix. You may need to use -webkit for Safari / Chrome, but test without first.

Categories

Resources