Is It Possible for this Script to be Generalised? - javascript

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>

Related

Restrict multiple image within div with JS/Jquery/CSS

On my webpage there are Gridster widgets.In these widgets initially the images are displayed from JSON(the name of image comes from JSON which I then put in src of image)
The users can also add images by clicking + button.User can also delete an image by clicking X button on the image.
The Problem I am facing
When the images coming from JSON are more or when the user manually adds more images then the images go out of widgets.
My Desired Output
Now I was trying to restrict those images in widget such that images will lay only in boundaries of div.
When there are more images the other existing images will resize and all of the images will fit in that area.
When I delete an image the other images will get bigger.In any case the entire area will be occupied by the images.
JS:
//JSON which I get from backend
var json = [{
"html": "https://d30y9cdsu7xlg0.cloudfront.net/png/802768-200.png,https://d30y9cdsu7xlg0.cloudfront.net/png/802768-200.png,https://d30y9cdsu7xlg0.cloudfront.net/png/802768-200.png", //3 Images
"col": 1,
"row": 1,
"size_y": 2,
"size_x": 2
}
];
//Loop which runs over JSON to generate <li> elements in HTML
for (var index = 0; index < json.length; index++) {
var images = json[index].html.split(',');
var imageOutput = "";
for (var j = 0; j < images.length; j++) {
imageOutput += '<div class="imagewrap"><img src=' + images[j] + '> <input type="button" class="removediv" value="X" /></div></div>';
}
gridster.add_widget('<li class="new" ><button class="addmorebrands" style="float: left;">+</button><button class="delete-widget-button" style="float: right;">-</button>' + imageOutput + '<textarea>' + json[index].html + '</textarea></li>', json[index].size_x, json[index].size_y, json[index].col, json[index].row);
}
//Function to delete an image from widget
$(document).on('click', '.removediv', function() {
$(this).closest('div.imagewrap').siblings('textarea')
$(this).closest('div.imagewrap').remove();
});
//Function to delete a widget
$(document).on("click", ".delete-widget-button", function() {
var gridster = $(".gridster ul").gridster().data('gridster');
gridster.remove_widget($(this).parent());
});
//Function to add mode Images to widgets from Modal
var parentLI;
$(document).on("click", ".addmorebrands", function() {
parentLI = $(this).closest('li');
$('#exampleModalCenter').modal('show');
$('#exampleModalCenter img').click(function() {
$(this).addClass('preselect');
$(this).siblings().removeClass('preselect');
selectedImageSRC = $(this).attr('src');
})
});
$('#add-image').click(function() {
parentLI.append('<div class="imagewrap"><img src="' + selectedImageSRC + '"> <input type="button" class="removediv" value="X" /></div>');
parentLI.children('textarea').append(', ' + selectedImageSRC);
$('#exampleModalCenter').modal('hide');
})
HTML
<div class="gridster">
<!-- <li> from JSON are placed here images are a part of li -->
<ul>
</ul>
</div>
The Fiddle so far
I am not sure if this can achieved just with CSS or will require any JS along with that
Update 1
I have tried with a lot of different CSS but still not able to get the expected output so if someone can help me with it would be really helpful
Maybe Gridster has a built in way to arrange items inside the grid cells, in case you have not found a way yet, try this.
I added some css:
.image-wrap-container{
min-height: 70%
}
.image-wrap-container div.imagewrap{
width: 33%
}
.text-area-wrap{
bottom: 0px;
left: 0px;
margin: 0px;
padding: 0px;
width: 100%;
height: 30%;
display: inline-flex;
}
.new.gs-w{
width: 200px;
min-height: 200px
}
.addmorebrands{
position: absolute;
left:0
}
.delete-widget-button{
position: absolute;
right:0
}
and restructured a little bit your html so images fit good within the cell, I hope that does not break anything, javascript was the least modified, only to add the images according to the new html structure.
Note: I tried to make the lis' height adjust to the amount of elements it contains, but [data-sizey="2"] kept getting in my way, so before throwing some probably unnecessary hack on it, try and achieve that using the library's own options, good luck.
Also, I noticed you were using this to update your textareas:
parentLI.children('.imagenames').val(function(i, selectedImageSRC) {return selectedImageSRC + ', '});
which won't work because you are using the same name for the argument, conflicting with the original selectedImageSRC variable. In case you are still having problems in that front, I replaced it with:
parentLI.children('.imagenames').val(function(i, currentContent) {return currentContent + ',' + selectedImageSRC + ', '});
Bonus Feature
The buttons for removing an image were to big for the images and covered quite a big part, so I took the liberty:
.removediv{
visibility: hidden
}
.imagewrap:hover .removediv{
visibility: visible
}
hope it helps

It is possible to load a CSS class into a Javascript/Jquery variable?

Im new in javascript/Jquery and I was wondering if is possible to load an existing css class into a variable to use it later.
Example:
/* CSS */
.view {
position: absolute;
height: 214px;
width:1964px;
padding:2px;
}
And in a javascript File load into a variable to use it like this:
//Javascript
var view = load(".view");
view.width // I get 1964px
view.height // I get 214px
Thanks.
You have to use the jQuery like this:
var view = $('.view');
Then you can iterate over all elements with the class view.
Do it by:
$.each(view, function(){
// then you can get all the width and height elements
// to use them, put them in an object or in an array
})
I'm not sure why you would do this unless you are amending HTML elements, but could you use getComputedStyle? Please note I am using good old JS here not jQuery...
/* CSS */
.view {
position: absolute;
height: 214px;
width:1964px;
padding:2px;
}
and in your JS
//Javascript
window.onload = function () {
var view = document.querySelector('.view'),
styles = getComputedStyle(view);
console.log(styles.width);
console.log(styles.height);
}
More info here
I haven't tested this but this is how I would go about it...

jQuery slider doesn't loop perfectly

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.

jquery .append iframe animation

I am trying to write a page that allows multiple buttons to be clicked on within my webpage and for iframes to load the content sliding down and knocking other content further down as they load.
I have tried various different ways to make this scroll up and down by using the jquery .animate functions but for some reason I just cannot get it to work.
Does anyone have any suggestions?
$(document).ready(function() {
$(".iframe").click(function() {
var file = $(this).attr("data-url");
var size = $(this).attr("data-size");
$(this).parent('article').append("<iframe class='articleframe' height='" + size + "' src='" + file + "'>Moo</iframe>");
$(this).siblings('.open').css('display','inline-block');
$(this).hide();
});
$(".open").click(function() {
$(this).hide();
$(this).siblings('.iframe').css('display','inline-block');
$(this).siblings('.articleframe').remove();
});
});
<article>
<h2>Querybox</h2>
<h5 class="button iframe" data-url="http://www.bbc.co.uk" data-size="900px" >Load another file</h5>
<h5 class='button open'>Hide Frame</h5>
</article>
For those of a visual dependence :) fiddlywiddly
You may have to tweak this to your specifications but this will do the trick:
Javascript:
Change '.append()' to '.prepend()'
$(document).ready(function () {
$(".iframe").click(function () {
var file = $(this).data("url");
var size = $(this).data("size");
var $one = $('#one');
$one.prepend("<h5 class='button open'>Hide Frame</h5><div class='frame-contain'><iframe class='articleframe' height=0 src='" + file + "'></iframe></div>");
var $wrap = $one.find('.frame-contain:first');
$wrap.animate({
height: size
}, {
duration: 1000,
step: function (now) {
$(this).height(now);
$(this).nextAll('.frame-contain').offsetParent().top += now;
$(this).find('iframe').height(now);
},
complete: function () {
$(".open").click(function () {
$(this).next('div').slideUp(1000);
$(this).fadeOut('slow');
});
}
});
});
});
I would also suggest re-arranging your elements but that's up to you. You can put your <h5> buttons into a <header> and the <iframe> creations into a <section>. Food for thought. That way your buttons don't go flying all over the place. My example utilizes this idea.
HTML:
<article>
<header>
<h2>Querybox</h2>
<h5 class="button iframe" data-url="http://www.bbc.co.uk" data-size="900">Load another file</h5>
</header>
<section id="one"></section>
</article>
CSS:
iframe, header, section, div {
clear: left;
position: relative;
display: block;
}
section, div {
width: 100%;
max-height: 100%;
}
.open {
clear: left;
display: block;
}
.articleframe {
float: left;
}
Since I am uncertain of your end-game here I made my best interpretation. Keep in mind this code is horribly inefficient the way it is set-up (so many selectors utilizing jQuery is very code intensive).
I would suggest you put more id tags on things and reference those using $(document.getElementById('id')) or $(document.querySelector('#id')). Also, using jQuery .width() and .height() takes a huge hit as well. You should use a javascript native selector and then apply .innerWidth = XX or .innerHeight = XX.
Normally performance at this small of scale isn't hugely important but given what you're displaying and the impact it has on the browser, it may help you.
For examples on the performance gain: JSPerf - innerHeight vs. .height()

Store positioning information from each element (JQuery/Javascript)

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});
});
}
});​

Categories