Im currently having a problem with my background image slider. It works perfectly fine the first time it runs, but the second time it 'bumps' the picture to the right when visible, instead of doing that when not visible. I hope some of you would take the time to look into this. Would be appreciated.
The code I'm using :
html part:
<div id="logo">
<img src="images/5.jpg">
<img src="images/6.jpg">
<img src="images/7.jpg">
<img src="images/8.jpg">
<img src="images/9.jpg">
<img src="images/10.jpg">
<img src="images/11.jpg">
</div>
css part:
#logo img {
min-height: 100%;
width:110%;
height: 100%;
position: fixed;
top:0px;
left: 0px;
}
JavaScript:
var slideshow = 0;
var currentImageIndex = 0;
var nextImage = function () {
var $imgs = $('#logo > img');
currentImageIndex++;
if (currentImageIndex > $imgs.length) {
currentImageIndex = 1;
}
$('#logo > img:nth-child(' + currentImageIndex + ')')
.fadeIn(function () { //.fadeIn() .show()
$(this).animate({
left: '-75px'
}, 8000, 'linear')
$(this).delay(100).fadeOut(nextImage), 1200; //.fadeOut() .show()
$(this).css({
left: '0px'
})
})
};
And it's triggered when clicked:
$( ".hexagoncontainer7" ).click(function() {
if (slideshow == 0) {
nextImage();
slideshow=1;
}
});
There are a few things to improve here. Hopefully this will give you a good start:
Currently, you're searching the DOM for your images twice upon each nextImage() call:
var $imgs = $('#logo > img'); and $('#logo > img:nth-child(' + currentImageIndex + ')').
Searching the DOM is computationally expensive, and you should do it only when necessary. Instead, if you need to work with the same elements over and over again (as you do with your images), select them once, and store them in variables for later use.
In the following lines:
$(this).animate({left: '-75px'}, 8000, 'linear')
$(this).delay(100).fadeOut(nextImage), 1200; //.fadeOut() .show()
$(this).css({left: '0px'})
a few things need to be fixed. First, line 3 removes any offset on your images left over from the previous pass. You want this step at the beginning of your sequence of steps (even before fadeIn), not at the end where its effect will be delayed.
Next, you do not need the delay(100) call before fadeOut(), because animate() lets you provide a callback which will be called once the animation completes. Supply your fadeOut code in the callback.
Also, notice that fadeOut first takes duration as a parameter, then the complete callback, i.e. your call should be fadeOut(1200, nextImage) if you want the fade-out effect to execute over 1200 ms.
Finally, jQuery lets you chain functions, which will save you a few calls to the jQuery function.
You should consider removing the click handler on #logo after the first click, to avoid unexpected behavior when users click more than once.
At the moment your images are stacked in reverse order over each other. You need to hide all but the first one with CSS.
Don't forget the semicolons and name your functions as actions, i.e. loadNextImage - your code will be easier to read.
Here is a working fiddle with all the changes: http://jsfiddle.net/stiliyan/4bo0258p/ (forked from the one isherwood posted)
In case you want to learn more best practices with jQuery I strongly recommend the Try jQuery course by CodeSchool.
Related
I'm very new to jQuery with little programming experience so please take that into consideration.
I created a timesaving script that will take the following two variables:
1) An element (which contains a single image) - imgelement
2) An image URL for the hover image - hoverimageurl
Code:
/* Image Hover Script (Start) */
var imgelement = "#element"; /* Element containing the original image */
var hoverimageurl = "http://www.domain.com/image.png2"; /* Image URL of the hover image */
jQuery(document).ready(function() {
/* Add CSS and a class to the original image to fix positioning and give it an identification */
jQuery(imgelement + " img").addClass("originalimage");
/* Prepend hover image to the element. Set the SRC to the hover image URL */
jQuery(imgelement).prepend('<img class="hoverimage" src="' + hoverimageurl + '">');
/* Fade out original image and fade in hover image on hover */
jQuery(imgelement).hover(function() {
jQuery(imgelement + " .originalimage").stop(true).fadeTo(1000, 0);
jQuery(imgelement + " .hoverimage").stop(true).fadeTo(1000, 1);
}, function() {
jQuery(imgelement + " .hoverimage").stop(true).fadeTo(1000, 0);
jQuery(imgelement + " .originalimage").stop(true).fadeTo(1000, 1);
});
});
/* Image Hover Script (End) */
/* Image Hover CSS (Start) */
#pix-fe .originalimage {
position: relative;
}
#pix-fe .hoverimage {
position: absolute;
width: 100%;
height: 100%;
}
/* Image Hover CSS (End) */
<div class="element">
<img src="http://www.domain.com/image1.png">
</div>
What the script does is fade to the hover image given from the hoverimageurl variable when the element is hovered. This works perfectly but I will want to use multiple instances of this script and to do so I would need to append the variable names with an incrementing number for each instance that I require. This is inefficient because the bulk of the script will need to be repeated per instance when ideally I would just like a variable list and one instance of the main script.
Is there any way at all that I can achieve generalisation of this code? I am aware of this but imgelement will always refer to the value against the imgelement variable specifically so with my knowledge I cannot see how this can be done.
Thank you very much for your time.
Nothing that little css can't handle :)
.image-switch {
position: relative;
}
.image-switch img {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
opacity: 1;
transition: opacity 1s
}
.image-switch:hover img:last-child{
opacity: 0
}
<span class="image-switch">
<img src="http://placehold.it/200x200/FF0000">
<img src="http://placehold.it/200x200">
</span>
You should really do something like this using css as it will be more reliable than js and easier to understand. That said this is a good opportunity to learn about jQuery plugins and how you can utilize it to make your code more DRY and reusable.
To add a new method to the jQuery object you can declare a function on jQuery.fn which is it's prototype.
in your function this will be bound to the collection of elements that you have selected, so you need to call each over it if you are planning on making the api work on collections.
From there you simply need to add your functionality to the function. Any arguments will be passed into your function so you can pass in settings at runtime. here it is used to pass in the img url.
The other basic function I used was jQuery.fn.find as we have the reference element that we can traverse the dom from, rather than using strings which are more confusing to my eyes.
To run the plugin you simply need to make a jQuery selector and call your new method. $('#selector').hoverImage('url')
Now that you know the basics of creating a jQuery plugin, you should really figure out how to achieve the same outcome with pure css.
/*
* jQuery hoverImage plugin
*
* #param {string} url url of the hover image
*/
(function() {
jQuery.fn.hoverImage = function(url) {
// loop over the element collection
this.each(function() {
// bind your functionality
var $this = $(this)
var $img = $this.find('img')
var $hover = $('<img class="hoverimage" src="' + url + '">')
$this.prepend($hover)
$this.hover(function() {
$img.stop().fadeTo(1000,0)
$hover.stop().fadeTo(1000,1)
}, function() {
$img.stop().fadeTo(1000,1)
$hover.stop().fadeTo(1000,0)
})
})
}
})()
// run your plugin
$('#element').hoverImage('http://lorempixel.com/400/200/')
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="element"></div>
I have the following jquery that slides a div horizontally:
$('.nextcol').click(function() {
$('.innerslide').animate({'left': '-=711px'}, 1000);
});
$('.prevcol').click(function() {
$('.innerslide').animate({'left': '+=711px'}, 1000);
});
What I want to happen is this... if the div.innerslide has a position that is left: 0px then I want to hide div.backarrow. If the position is not left: 0px, then it shows it.
Any help would be greatly appreciated.
EDIT (added HTML Markup)
<div class="backarrow prevcol">
<div id="mainleft" class="overflowhidden">
<div class="innerslide">
<div class="col">my content including next</div>
<div class="col">my content including next</div>
<div class="col">my content including next</div>
</div>
</div>
Try this:
if ($('.innerslide').css("left") == 0) {
$('div.backarrow').hide();
} else {
$('div.backarrow').show();
}
Fix for Double-Click Issue:
From what you described in your comment about the issue when the visitor double-clicks, it sounds like the double-click is causing two of the animation events to fire. To keep this from happening, you can either disable the click handler while the animation is running and re-enable it once it is finished, or you can try to write a new thread to continually check the element's position. One of these solutions is not a good idea - I'll let you figure out which one :) - but the other actually has a very simple solution that requires little change to your existing code (and may actually reduce your overhead by a teeny weeny amount):
$('.nextcol').on("click.next", function() {
$('.innerslide').animate({'left': '-=711px'}, 1000, showHideBack());
$(this).off("click.next");
});
$('.prevcol').on("click.prev", function() {
$('.innerslide').animate({'left': '+=711px'}, 1000, showHideForward());
$(this).off("click.prev");
});
Then add this this line to showHideBack() (and a complementary one to showHideForward() if you are using that):
$('.nextcol').on("click.next".....
I suggest that you write a function to set each click handler and another to remove each one. This will make your live very easy and the whole solution should reduce overhead by removing unnecessary click handlers while the animation is running.
Note: the animation method often calls its callback before the animation finishes. As such, you may wish to use a delay before calling the showHide... method(s).
Let me know if you have any questions. Good luck! :)
UPDATE:
Here is the updated version of the fiddle you gave me with all bugs ironed out. It looks like I misunderstood part of your goal in my original solution, but I straightened it out here. I have also included the updated jQuery, here:
var speed = 1000;
var back = $("div.backarrow");
var next = $(".nextcol");
var prev = $(".prevcol");
var inner = $(".innerslide");
function clickNext(index) {
next.off("click.next");
inner.animate({
'left': '-=711px'
}, speed, function() {
back.show(); //this line will only be hit if there is a previous column to show
next.delay(speed).on("click.next", function() {
clickNext();
});
});
}
function clickPrev() {
prev.off("click.prev");
inner.animate({
'left': '+=711px'
}, speed, function() {
if (inner.css("left") == "0px") {
back.delay(speed).hide();
prev.delay(speed).on("click.prev", function() {
clickPrev();
});
} else {
back.delay(speed).show();
prev.delay(speed).on("click.prev", function() {
clickPrev();
});
}
});
}
next.on("click.next", function() {
clickNext();
});
prev.on("click.prev", function() {
clickPrev();
});
I was going to also include a condition to check if you were viewing the last column, but, as I don't know what your final implementation will be, I didn't know if it would be applicable. As always, let me know if you need help or clarification on any of this. :)
You could try the step option — a callback function that is fired at each step of the animation:
$('.prevcol').click(function() {
$('.innerslide').animate({ left: '+=711px' },
{
duration: 1000,
step: function(now, fx) {
if (now === 0 ) {
$('div.backarrow').hide();
} else {
$('div.backarrow').show();
}
}
});
});
More examples of usage in this article The jQuery animate() step callback function
Pleasantries
I've been playing around with this idea for a couple of days but can't seem to get a good grasp of it. I feel I'm almost there, but could use some help. I'm probably going to slap myself right in the head when I get an answer.
Actual Problem
I have a series of <articles> in my <section>, they are generated with php (and TWIG). The <article> tags have an image and a paragraph within them. On the page, only the image is visible. Once the user clicks on the image, the article expands horizontally and the paragraph is revealed. The article also animates left, thus taking up the entire width of the section and leaving all other articles hidden behind it.
I have accomplished this portion of the effect without problem. The real issue is getting the article back to where it originally was. Within the article is a "Close" <button>. Once the button is clicked, the effect needs to be reversed (ie. The article returns to original size, only showing the image, and returns to its original position.)
Current Theory
I think I need to retrieve the offset().left information from each article per section, and make sure it's associated with its respective article, so that the article knows where to go once the "Close" button is clicked. I'm of course open to different interpretations.
I've been trying to use the $.each, each(), $.map, map() and toArray() functions to know avail.
Actual Code
/*CSS*/
section > article.window {
width:170px;
height:200px;
padding:0;
margin:4px 0 0 4px;
position:relative;
float:left;
overflow:hidden;
}
section > article.window:nth-child(1) {margin-left:0;}
<!--HTML-->
<article class="window">
<img alt="Title-1" />
<p><!-- I'm a paragraph filled with text --></p>
<button class="sClose">Close</button>
</article>
<article class="window">
<!-- Ditto + 2 more -->
</article>
Failed Attempt Example
function winSlide() {
var aO = $(this).parent().offset()
var aOL = aO.left
var dO = $(this).offset()
var dOL = dO.left
var dOT = dO.top
var adTravel = dOL-aOL
$(this).addClass('windowOP');
$(this).children('div').animate({left:-(adTravel-3)+'px', width:'740px'},250)
$(this).children('div').append('<button class="sClose">Close</button>');
$(this).unbind('click', winSlide);
}
$('.window').on('click', winSlide)
$('.window').on('click', 'button.sClose', function() {
var wW = $(this).parents('.window').width()
var aO = $(this).parents('section').offset()
var aOL = aO.left
var pOL = $(this).parents('.window').offset().left
var apTravel = pOL - aOL
$(this).parent('div').animate({left:'+='+apTravel+'px'},250).delay(250, function() {$(this).animate({width:wW+'px'},250); $('.window').removeClass('windowOP');})
$('.window').bind('click', winSlide)
})
Before you go scratching your head, I have to make a note that this attempt involved an extra div within the article. The idea was to have the article's overflow set to visible (.addclass('windowOP')) with the div moving around freely. This method actually did work... almost. The animation would fail after it fired off a second time. Also for some reason when closing the first article, the left margin was property was ignored.
ie.
First time a window is clicked: Performs open animation flawlessly
First time window's close button is clicked: Performs close animation flawlessly, returns original position
Second time SAME window is clicked: Animation fails, but opens to correct size
Second time window's close button is clicked (if visible): Nothing happens
Thank you for your patience. If you need anymore information, just ask.
EDIT
Added a jsfiddle after tinkering with Flambino's code.
http://jsfiddle.net/6RV88/66/
The articles that are not clicked need to remain where they are. Having problems achieving that now.
If you want to go for storing the offsets, you can use jQuery's .data method to store data "on" the elements and retrieve it later:
// Store offset before any animations
// (using .each here, but it could also be done in a click handler,
// before starting the animation)
$(".window").each(function () {
$(this).data("closedOffset", $(this).position());
});
// Retrieve the offsets later
$('.window').on('click', 'button.sClose', function() {
var originalOffset = $(this).data("originalOffset");
// ...
});
Here's a (very) simple jsfiddle example
Update: And here's a more fleshed-out one
Big thanks to Flambino
I was able to create the effect desired. You can see it here: http://jsfiddle.net/gck2Y/ or you can look below to see the code and some explanations.
Rather than having each article's offset be remembered, I used margins on the clicked article's siblings. It's not exactly pretty, but it works exceptionally well.
<!-- HTML -->
<section>
<article>Click!</article>
<article>Me Too</article>
<article>Me Three</article>
<article>I Aswell</article>
</section>
/* CSS */
section {
position: relative;
width: 404px;
border: 1px solid #000;
height: 100px;
overflow:hidden
}
article {
height:100px;
width:100px;
position: relative;
float:left;
background: green;
border-right:1px solid orange;
}
.expanded {z-index:2;}
//Javascript
var element = $("article");
element.on("click", function () {
if( !$(this).hasClass("expanded") ) {
$(this).addClass("expanded");
$(this).data("originalOffset", $(this).offset().left);
element.data("originalSize", {
width: element.width(),
height: element.height()
});
var aOffset = $(this).data("originalOffset");
var aOuterWidth = $(this).outerWidth();
if(!$(this).is('article:first-child')){
$(this).prev().css('margin-right',aOuterWidth)
} else {
$(this).next().css('margin-left',aOuterWidth)
}
$(this).css({'position':'absolute','left':aOffset});
$(this).animate({
left: 0,
width: "100%"
}, 500);
} else {
var offset = $(this).data("originalOffset");
var size = $(this).data("originalSize");
$(this).animate({
left: offset + "px",
width: size.width + "px"
}, 500, function () {
$(this).removeClass("expanded");
$(this).prev().css('margin-right','0')
$(this).next().css('margin-left','0')
element.css({'position':'relative','left':0});
});
}
});
http://jsfiddle.net/jmPCt/18/
I'm quite new to JS and jQuery. I've written all the code by hand in the link above. It works and does what I want it to save for but one thing. If you click rapidly on the 'next' link, you'll see either a flash of the next container to display or, if you click rapidly enough, the code will display two containers but I only want one to show only at a time. Is there some way of handling this in jQuery? I've tried using stops as discussed here: How to prevent jquery hover event from firing when not complete? but this does not solve the issue.
You are looking for .stop(). It's implementation changes with the desired behavior but the documentation should clear that up for you: http://api.jquery.com/stop
Here is a demo: http://jsfiddle.net/jmPCt/19/
Because of how .stop() works, when you use it with .fadeIn() or .fadeOut() you can chop-up your animations to the point where they no longer work. The best fix I've found is to always animate to absolute values with .fadeTo(): http://api.jquery.com/fadeTo
Here is the code I added to your JSFiddle, this overwrites the default .fadeIn() and .fadeOut() jQuery functions with ones that use .fadeTo() and .stop():
$.fn.fadeOut = function (duration, callback) {
$(this).stop().fadeTo(duration, 0, function () {
$(this).css('display', 'none');
if (typeof callback == 'function') {
callback();
}
});
};
$.fn.fadeIn = function (duration, callback) {
$(this).css('display', 'block').stop().fadeTo(duration, 1, function () {
if (typeof callback == 'function') {
callback();
}
});
};
Update
If you set the position property for the "slide" elements then they can animate on top of each other which will remove the jumpiness that your code exhibits:
HTML --
<div id="controls">
<div id="countah"></div>
<a href="#" id=prev>prev</a> |
<a href="#" id=next>next</a>
</div>
CSS --
.js .staceyPort {
display: none;
position : absolute;
top : 0;
left : 0;
}
#controls{
position : fixed;
bottom : 0;
left : 0;
z-index : 1000;
background : gold;
}
Here is a demo: http://jsfiddle.net/jmPCt/21/
I can't get the actually LazyLoad plugin to work for me so I am trying to write my own with. Currently I have a list of images loading inside of a DIV. They are pulled by a PHP query to the mysql database. The DIV scroll is set to auto. The code I am using is:
<div id="b1" style="overflow:auto;">
<?PHP $result = mysql_query("SELECT * FROM images");
while($row = mysql_fetch_assoc($result)) {
echo "<img src='$row[photo]' style='display:none'> <br>";
}
</div>
<script type="text/javascript">
function imgCheck() {
var position = $("img").offset().top;
var scrollCheck = $("#b1").scrollTop() + $("#b1").height();
if ( scrollCheck > position) {
$("img").fadeIn("fast");
}
$("#b1").scroll( function() { imgCheck() } );
</script>
Although this is not quite working for me. Could anyone help me out or shoot out some suggestions?
A couple of things:
As the others have already said, your code has syntax errors - both with the PHP and the Javascript.
If you use display: none, the elements will not take up any height, thus causing the entire thing to become unscrollable and fail.
The first few elements should be visible without the user having to start scrolling
Taking these into consideration, we can try writing this this way:
// Cache the containing element and it's height
var b1 = $('#b1'),
h = b1.height();
// Insert 20 img's - simulating server-side code
for(var i = 0; i < 20; i++){
$('<img />', {
src: 'http://placehold.it/100x100',
alt: '',
class: 'hidden',
width: 100,
height: 100
// visibility: hidden to retain it's size
}).css('visibility', 'hidden').appendTo(b1);
}
b1.scroll(function(){
// Loop through only hidden images
$('img.hidden').each(function(){
// $(this).position().top calculates the offset to the parent
// So scrolling is already taken care of here
if(h > $(this).position().top){
// Remove class, set visibility back to visible,
// then hide and fade in image
$(this).css('visibility', 'visible')
.hide()
.removeClass('hidden')
.fadeIn(300);
} else {
// No need to check the rest - everything below this image
// will always evaluate to false - so we exit out of the each loop
return false;
}
});
// Trigger once to show the first few images
}).trigger('scroll');
See a demo of this here: http://jsfiddle.net/yijiang/eXSXm/2
If all of the images are hidden, then there will never be a 'scroll' even called as the element will never scroll.
What exactly are you trying to achieve? If it is to have new images that weren't previously visible, but now may be, become visible then you will have to do something like;
<div id="b1" style="overflow:auto;">
<?php $result = mysql_query("SELECT * FROM images");
while($row = mysql_fetch_assoc($result)) {
echo "<img src='$row[photo]' style='visibility:hidden'> <br>";
} ?>
</div>
<script type="text/javascript">
function imgCheck() {
var scrollCheck = $("#b1").scrollTop() + $("#b1").height();
$("#b1 img").each(function() {
var position = $(this).offset().top;
if (scrollCheck > position) {
$(this).fadeIn("fast");
}
});
}
$(document).ready(imgCheck);
$("#b1").scroll(imgCheck);
</script>
Note that I haven't tested the above, and I can imagine that it will result in all images being shown immediately, since all of their 'top's will be 0 due to them all being hidden and not having their position effected by previous images in the DOM.
Edit
I've changed the code above so the img's have visibility:hidden, which should give them a height and take up space in the DOM
I've been testing it, it seems to work with some modifications:
http://jsfiddle.net/antiflu/zEHtu/
Some things I had to change:
I added the closing } for the imgCheck() function, you forgot it
Some items need to be visible from the beginning on, otherwise the scrollbar never appears and imgCheck() is never called.
OK the problem with the above was that the images don't fade in separately upon scroll. I got it to work though with some modifications:
http://jsfiddle.net/antiflu/GdzmQ/
What I changed:
I changed the display: none to opacity: 0 so that any invisible picture has at least an empty placeholder of the same size, so that the scroll bar will be visible.
I then fade in using animate to opacity: 1
I used jQuery each() to iterate over the images and check for each of them if they should or should not be faded in (instead of checking for all, like before).
I wrapped the images in DIV's. I don't think it's necessary, but it doesn't harm either.
I tagged each image with an id, so that I can single them out for the fadein.
There are still some esthetic issues but this should help you on your way.