I'm trying make this image move 16px every click, but it's not moving. I've tried this:
But that only moves it once. So I tried something different that I found online.
Here is my code:
Html:
document.getElementById('player').style.left += "-16px";
function left() {
var left = parseInt(player.style.left);
player.style.left = (left +16) + "px";
}
<img id="player" src="characters/prisoner-down.png"></img>
<img id="leftbtn" src="icons/left.png" onclick="left();"></img>
Edit: if it's useful to know, there are gonna be three more buttons for the other directions and the image I want to move is in another DIV from the image that moves it. On my editor it says that the symbol ` is invalid, so I'd like to avoid it.
You can create a function that get the direction of the translation and the number of pixels to increment, then return a function that set style.transform of the element clicked, using a closure you can update the amount of pixels to translate, try this:
function transform(side, n) {
let translatePixels = 0;
if (side == 'right') {
return function(e) {
translatePixels += n;
e.target.style.transform = `translate(${translatePixels}px)`;
}
}
if (side == 'left') {
return function(e) {
translatePixels -= n;
e.target.style.transform = `translate(${translatePixels}px)`;
}
}
}
const translateRight = transform('right', 16);
const translateLeft = transform('left', 16);
<img onclick="translateRight(event);" src="https://images.rappi.com.ar/products/2311265-1622567743906.png?d=200x200&e=webp"></img>
<img onclick="translateLeft(event);" src="https://cdn.domestika.org/c_fill,dpr_auto,f_auto,h_256,pg_1,t_base_params,w_256/v1499705651/avatars/000/536/178/536178-original.jpg?1499705651" style="right: 0; position:absolute"></img>
Give position property to the img which you want to move.
Like position: relative;, position: absolute; etc.
By positioning them you can use top, left, right and top properties.
don't use position: static; on that img cause above property has no effect on static element.
document.getElementById('player').style.left = "-16px";
function left() {
let left = parseInt(player.style.left);
player.style.left = `${parseInt(left+16)}px`;
}
img{
/*important for using left, right, top and bottom properties*/
position: relative;
}
<img id="player" src="https://i.postimg.cc/44pXvSwD/pngwing-com.png"></img>
<img id="leftbtn" src="https://i.postimg.cc/FRkMDYvr/Pngtree-right-arrow-flat-multi-color-3777297.png" onclick="left();"></img>
Related
I would like to build an image gallery like this where pictures keep looping automatically but at the same time, it's possible to interact through the gallery using the mouse wheel as well. I would also achieve an effect like that where images, instead of scrolling out of the screen shrink on the side.
So I was trying to understand where to start and I couldn't find any library for the task so I tried from scratch using vanilla js, this is my attempt.
var testArray = document.querySelectorAll(".image");
var totImg = testArray.length;
var contIterazioni = 0;
var test = testArray[contIterazioni];
var bounding = test.getBoundingClientRect();
function LoopFunction() {
setInterval(galleryScroll, 20);
}
function galleryScroll() {
test = testArray[contIterazioni];
bounding = test.getBoundingClientRect();
if (bounding.left == 0) {
//console.log("immagine bordo sx");
if (test.width > 0) {
test.width = test.width - .25;
} else {
contIterazioni = contIterazioni + 1;
if (contIterazioni >= totImg){
contIterazioni = 0;
}
}
}
}
LoopFunction();
body{
margin: 0;
padding: 0;
box-sizing: border-box;
}
.container {
width: 100vw;
height: 100vh;
display: flex;
overflow: hidden;
}
<div class="container">
<img src="https://media-cdn.tripadvisor.com/media/photo-s/0e/eb/ad/3d/crazy-cat-cafe.jpg" alt="" class="image">
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/d6/Chairman_Meow_Bao.jpg/1200px-Chairman_Meow_Bao.jpg" alt="" class="image">
<img src="https://static.scientificamerican.com/sciam/cache/file/92E141F8-36E4-4331-BB2EE42AC8674DD3_source.jpg" alt="" class="image">
<img src="https://cdn.britannica.com/91/181391-050-1DA18304/cat-toes-paw-number-paws-tiger-tabby.jpg" alt="" class="image">
<img src="https://www.orlandocatcafe.com/wp-content/uploads/2020/07/14.png" alt="" class="image">
</div>
Unfortunately, I couldn't figure out how to loop the images. Can you help me understand how to do it? Do you think my approach can be a good one? or would you recommend other ways to get that effect?
Thanks!
Use for loop when you go through all images to set their width to their initial value. Also add extra three images from start of the carousel so there wouldn't be empty space when it comes to an end.
var testArray = document.querySelectorAll(".image");
var totImg = testArray.length;
var contIterazioni = 0;
var test = testArray[contIterazioni];
var bounding = test.getBoundingClientRect();
var imgsWidths = []
window.onload = () => {
for(i = 0; i < testArray.length; i++) {
imgsWidths[i] = testArray[i].width
}
}
function galleryScroll() {
test = testArray[contIterazioni];
bounding = test.getBoundingClientRect();
if (bounding.left == 0) {
if (test.width > 0) {
test.width = test.width - 2;
} else {
contIterazioni = contIterazioni + 1;
if (contIterazioni >= totImg - 3){
for(i = 0; i < testArray.length; i++) {
testArray[i].width = imgsWidths[i]
}
contIterazioni = 0;
}
}
}
window.requestAnimationFrame(galleryScroll)
}
window.requestAnimationFrame(galleryScroll);
body{
margin: 0;
padding: 0;
box-sizing: border-box;
}
.container {
width: 100vw;
height: 100vh;
display: flex;
overflow: hidden;
}
<div class="container">
<img src="https://media-cdn.tripadvisor.com/media/photo-s/0e/eb/ad/3d/crazy-cat-cafe.jpg" alt="" class="image">
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/d6/Chairman_Meow_Bao.jpg/1200px-Chairman_Meow_Bao.jpg" alt="" class="image">
<img src="https://static.scientificamerican.com/sciam/cache/file/92E141F8-36E4-4331-BB2EE42AC8674DD3_source.jpg" alt="" class="image">
<img src="https://cdn.britannica.com/91/181391-050-1DA18304/cat-toes-paw-number-paws-tiger-tabby.jpg" alt="" class="image">
<img src="https://www.orlandocatcafe.com/wp-content/uploads/2020/07/14.png" alt="" class="image">
<img src="https://media-cdn.tripadvisor.com/media/photo-s/0e/eb/ad/3d/crazy-cat-cafe.jpg" alt="" class="image">
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/d6/Chairman_Meow_Bao.jpg/1200px-Chairman_Meow_Bao.jpg" alt="" class="image">
<img src="https://static.scientificamerican.com/sciam/cache/file/92E141F8-36E4-4331-BB2EE42AC8674DD3_source.jpg" alt="" class="image">
</div>
While keeping your same approach, this should work similarly as the example you provided. If you want the slideshow to keep it's width, all images should have the same width. Your error was not adding back the image that dissapeared to the container, at the end and with it's original width, or as in this case expanding again.
// From https://www.javascripttutorial.net/javascript-queue/
function Queue() {
this.elements = [];
}
Queue.prototype.enqueue = function(e) {
this.elements.push(e);
};
Queue.prototype.dequeue = function() {
return this.elements.shift();
};
Queue.prototype.isEmpty = function() {
return this.elements.length == 0;
};
Queue.prototype.peek = function() {
return !this.isEmpty() ? this.elements[0] : undefined;
};
Queue.prototype.length = function() {
return this.elements.length;
}
let images = new Queue();
document.querySelectorAll(".image").forEach(img => images.enqueue(img));
var totImg = images.length();
var first = images.dequeue();
var last;
var firstWidth = first.width;
var lastWidth = 0.0;
var bounding = first.getBoundingClientRect();
var originalWidth = first.width;
var lastOriginalWidth = 0.0;
var step = 1.0;
var lastStep = 0.0;
function LoopFunction() {
setInterval(galleryScroll, 20);
}
function galleryScroll() {
bounding = first.getBoundingClientRect();
if (bounding.left == 0) {
//console.log("immagine bordo sx");
if (first.width > 0) {
firstWidth -= step;
if (firstWidth < 0) {
firstWidth = 0;
}
first.style.width = firstWidth + 'px';
} else {
if (last && last.width != lastOriginalWidth) {
last.width = lastOriginalWidth;
}
let container = document.querySelector('.container');
container.removeChild(first);
last = first;
lastOriginalWidth = originalWidth;
last.width = 0.0;
lastWidth = 0.0;
images.enqueue(last);
container.appendChild(last);
first = images.dequeue();
originalWidth = first.width;
firstWidth = originalWidth;
lastStep = step * lastOriginalWidth / originalWidth;
}
}
if (last && last.width <= lastOriginalWidth) {
lastWidth += lastStep;
last.style.width = lastWidth + 'px';
if (last.width > lastOriginalWidth) {
last.width = lastOriginalWidth;
}
}
}
LoopFunction();
body {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.container {
width: 100vw;
height: 100vh;
display: flex;
overflow: hidden;
}
<div class="container">
<img src="https://media-cdn.tripadvisor.com/media/photo-s/0e/eb/ad/3d/crazy-cat-cafe.jpg" alt="" class="image">
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/d6/Chairman_Meow_Bao.jpg/1200px-Chairman_Meow_Bao.jpg" alt="" class="image">
<img src="https://static.scientificamerican.com/sciam/cache/file/92E141F8-36E4-4331-BB2EE42AC8674DD3_source.jpg" alt="" class="image">
<img src="https://cdn.britannica.com/91/181391-050-1DA18304/cat-toes-paw-number-paws-tiger-tabby.jpg" alt="" class="image">
<img src="https://www.orlandocatcafe.com/wp-content/uploads/2020/07/14.png" alt="" class="image">
</div>
Update #1
As requested by OP, here is some further explenation of the changes I made:
I used a simple queue from javascripttutorial to have a data structure that reflects the objective, i.e. having images that move from right to left, and once they "exit" the scene, can get enqueued again.
Following this concept, we need pointers to both head and tail of the queue in order to update their animation, in particular first, firstWidth and originalWidth are used respectively to point to the head, keep track of its current width during the animation and the originalWidth to which it should return when re-entering the scene. Similarly, this happens for the last as well.
Once this is in place, our loop will be made of two parts, one that handles the head animation and moves the images in the queue when the head image reaches width == 0 and one that handles the tail re-entering the scene and expanding. To achieve continuity, we use a simple proportion after we have our new queue: step : originalWidth = lastStep : lastOriginalWidth, which we use to determine at each frame how much respectively the head and tail images compress and expand. as step is a fixed parameter, lastStep is easily computed through simple arithmetics: lastStep = step * lastOriginalWidth / originalWidth.
The code:
let container = document.querySelector('.container');
container.removeChild(first);
last = first;
lastOriginalWidth = originalWidth;
last.width = 0.0;
lastWidth = 0.0;
images.enqueue(last);
container.appendChild(last);
first = images.dequeue();
originalWidth = first.width;
is just a contextual switch over the changing the queue, where the pointers and respective data get update to reflect the changes in the data structure. So the first becomes last, the next image in the queue is the new compressing first, as such originalWidth shifts.
Let's go further
Now, to achieve your desired implementation, this still needs some work. First of all, the queue should only reflect the actual carousel, not all the images you have, so ideally you would have a first static data structure with all the images and respective metadata, in this case we mainly need their original width. then you would have a queue representing the images that are being animated.
Now, you can either reuse this code and change the width of the images to be all equal and fit the viewport you are using for your animation, which should work fine. The other option is to mirror the code for the head image to the tail, have a fixed step, so while the head compresses of step pixels, the tail expands of the same step pixels, and when the tail reaches maximum width, introduce a new image at the end of the queue which becomes the new tail.
To go in further detail on the last, the algorithm would look something like this:
initialize static list of image containers with their respective width (imgList);
initialize an empty queue (imgQueue);
initialize a pointer pointing to the last image introduced in the queue (listPtr);
get the width of the viewport your carousel should fill (vpWidth);
while you have not filled such width, keep adding images to the queue and moving the listPtr left in the imgList;
have looping function similar to the one above but where the if over last replicates similar steps to the first ones;
This should be everything, I will work on the code in a while, now I need some sleep ;).
Update #2
Okay! Worked out something that actually works decently. If you have suggestions please let me know, in the meanwhile, here it is:
// From https://www.javascripttutorial.net/javascript-queue/
function Queue() {
this.elements = [];
}
Queue.prototype.enqueue = function(e) {
this.elements.push(e);
};
Queue.prototype.dequeue = function() {
return this.elements.shift();
};
Queue.prototype.isEmpty = function() {
return this.elements.length == 0;
};
Queue.prototype.first = function() {
return !this.isEmpty() ? this.elements[0] : undefined;
};
Queue.prototype.length = function() {
return this.elements.length;
};
// Added function to peek last element in queue
Queue.prototype.last = function() {
return !this.isEmpty() ? this.elements[this.elements.length - 1] : undefined;
};
// Added function that pops the head, enqueues it and returns it
Queue.prototype.shift = function() {
const head = this.dequeue();
this.enqueue(head);
return head;
}
// Returns a queue of HTMLElements based on the given class
function loadQueue(className) {
const rQueue = new Queue();
document.querySelectorAll('.' + className).forEach(image => rQueue.enqueue(image));
return rQueue;
}
// Queue of images to be added to the animation queue
const imgQueue = loadQueue('image');
// Images being animated inside the div
const imgAnimQueue = new Queue();
// To limit calls to the carousel
const carousel = $('.container');
function init() {
// Width of the viewport being used for the animation
// TODO: this shoud update whenever the page is resized, or at least the div
const vpWidth = carousel.width();
// Keeps track of the width of the images added to the animation queue
var currentFilledWidth = 0;
// Now that we have loaded the static information, let's clear the div
// that will be used as carousel, so that we can add the correct number
// of images back in
carousel.empty();
// Filling the animation queue
while (currentFilledWidth < vpWidth) {
// In order not to change the static data, we clone the image HTMLElement
const img = $(imgQueue.shift()).clone();
// Enqueuing the new image in the animation queue
imgAnimQueue.enqueue(img);
// Adding this image into the carousel
img.appendTo(carousel);
currentFilledWidth = currentFilledWidth + img.width();
// If we overflow the carousel width, we set the tail image to fill
if (currentFilledWidth > vpWidth) {
const overflow = currentFilledWidth - vpWidth;
img.width(img.width() - overflow);
currentFilledWidth -= overflow;
}
}
const step = 1;
var firstWidth = imgAnimQueue.first().width();
var lastWidth = imgAnimQueue.last().width();
// Now the loop can start
window.requestAnimationFrame(animateCarousel);
// Main function that animates the carousel
function animateCarousel() {
let first = imgAnimQueue.first();
let last = imgAnimQueue.last();
// If the image is still not disappeared, keep compressing it
if (firstWidth > 0) {
firstWidth -= step;
if (firstWidth < 0) firstWidth = 0;
first.width(firstWidth);
// Otherwise, remove it from the carousel and update all the data structs
} else {
first.remove();
imgAnimQueue.dequeue();
if (imgAnimQueue.first() != last) {
first = imgAnimQueue.first();
firstWidth = first.width();
} else {
first = last;
firstWidth = lastWidth;
imgAnimQueue.enqueue($(imgQueue.shift()).clone());
last = imgAnimQueue.last();
lastWidth = last.width();
last.appendTo(carousel);
}
}
if (lastWidth <= last.attr("data-original-width")) {
lastWidth += step;
// The image has completely expanded, let's introduce the next one
if (lastWidth >= last.attr("data-original-width")) {
last.width(last.attr("data-original-width"));
imgAnimQueue.enqueue($(imgQueue.shift()).clone());
last = imgAnimQueue.last();
last.width(0);
lastWidth = last.width();
last.appendTo(carousel);
// Otherwise just keep expanding it
} else {
last.width(lastWidth);
}
}
window.requestAnimationFrame(animateCarousel);
}
}
body {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.container {
width: 100vw;
height: 100vh;
display: flex;
overflow: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
var loadedImages = 0;
const count = 4;
function loadImage(img) {
if (loadedImages != count) {
$(img).attr("data-original-width", $(img).width());
loadedImages += 1;
if (loadedImages == count) init();
}
}
</script>
<div class="container">
<img onload="loadImage(this)" src="https://media-cdn.tripadvisor.com/media/photo-s/0e/eb/ad/3d/crazy-cat-cafe.jpg" alt="1" class="image">
<img onload="loadImage(this)" src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/d6/Chairman_Meow_Bao.jpg/1200px-Chairman_Meow_Bao.jpg" alt="2" class="image">
<img onload="loadImage(this)" src="https://cdn.britannica.com/91/181391-050-1DA18304/cat-toes-paw-number-paws-tiger-tabby.jpg" alt="3" class="image">
<img onload="loadImage(this)" src="https://www.orlandocatcafe.com/wp-content/uploads/2020/07/14.png" alt="4" class="image">
</div>
I am trying to create an image carousel and I want the images to loop through the array. The code is clearly working because the "no click" alert shows but I don't understand what I am doing wrong.
Here is JS. I know the problem is most likely the if statement. I'm a beginner in JS and haven't quite understood how to code that bit properly. In some sort of pseudo code, it would ideally be "if btn is clicked, then show the next image".
let images = ["img1.jpg", "img2.jpg", "img3.jpg"];
let img = images.length;
let x = 0;
function whenClicked(e) {
for (let i = 0; i < img; i++) {
if (x == 0) {
document.getElementById("img1").style.backgroundImage = ++img;
} else alert("no click!")
}
}
const btnLeft = document.getElementById("btn-left");
const btnRight = document.getElementById("btn-right");
btnLeft.addEventListener("click", whenClicked);
btnRight.addEventListener("click", whenClicked);
And here HTML.
<div class="slide-container">
<div class="slide" id="img1">
<i class="fas fa-arrow-circle-left fa-3x" id="btn-left" ></i>
<i class="fas fa-arrow-circle-right fa-3x" id="btn-right" ></i>
</div>
</div>
CSS.
.slide-container {
}
.slide {
height: 789px;
width: 100%;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
.slide:nth-child(1) {
background-image: url(../img/img1.jpg);
}
.slide:nth-child(2) {
background-image: url(../img/img2.jpg);
}
.slide:nth-child(3) {
background-image: url(../img/img3.jpg);
}
#btn-left {
position: absolute;
top: 400px;
}
#btn-right {
position: absolute;
left: 1380px;
top: 400px;
}
Suggestions on what to do to improve would be deeply appreciated!
You might be overcomplicating your approach. Try to keep track of which image is currently being shown and what image should be next. You are almost doing this, with I assume is your intent, with the x variable. This is a great way to select an image from the array by simply doing images[x], which translates to images[0] at the start.
So if you want to develop a system which changes the background image of the slide, then your buttons would have to change the value of the x variable. Let's give that variable a more clear name, like currentIndex which represents the current index in the images array. Or to be clear, the current image we are seeing.
When your right button is clicked, you'll want the next image. The next images in the images array is images[1]. All you have to do is update the currentImage value with +1 or ++ and select the next image with images[currentIndex] to get the next one.
Same goes for the left button which should remove 1 from the currentImage with either -1 or -- to go back one index in the images array. Then select the image the same way with images[currentIndex].
Now for both these buttons you'll need to check if you go past the start or the end of the images array. currentIndex cannot be lower than 0 and with 3 images in the array, the currentIndex value should not be higher than 2, since we are counting with [0, 1, 2] in the images array.
Below I've made an implementation of this system. I hope this is what you intended to make. If I haven't been clear or you have question, then don't hesitate to ask.
let images = ["img1.jpg", "img2.jpg", "img3.jpg"];
let amountOfImages = images.length;
let currentIndex = 0;
const slide = document.getElementById("img1");
const btnLeft = document.getElementById("btn-left");
const btnRight = document.getElementById("btn-right");
function prevImage() {
if (currentIndex > 0) { // Can't go lower than 0.
currentIndex--;
updateImage();
}
}
function nextImage() {
if (currentIndex < amountOfImages - 1) { // Can't go higher than the amount of images present.
currentIndex++;
updateImage();
}
}
function updateImage() {
slide.style.backgroundImage = images[currentIndex];
}
btnLeft.addEventListener("click", prevImage);
btnRight.addEventListener("click", nextImage);
let currentImage = 0;
let images = ["img1.jpg", "img2.jpg", "img3.jpg"];
function whenClicked(e) {
if(e.target.id == 'btn-left') {
currentImage--;
} else {
currentImage++;
}
if(currentImage > images.length-1) {
currentImage = 0;
}
if(currentImage < 0) {
currentImage = images.length-1;
}
document.getElementById("img1").style.backgroundImage = url('img/'+images[currentImage]);
}
I've run into a problem with running a loop to trigger a mousemove event on my HTML/CSS.
I know I can go through and get every individual ID on the HTML tags to execute the code the way I want. But I know there is a better way to do it with a loop of some sort and use far less code.
The images should follow the mouse while moving over the div with class mycard. Any suggestions or ideas on how to get it working properly would be very much appreciated.
I've tried running a loop to add the classes to divs but had no luck.
var mouseHover = document.getElementById('moveHover');
window.onmousemove = function(e) {
var x = e.clientX;
var y = e.clientY;
mouseHover.style.top = (y + 20) + 'px';
mouseHover.style.left = (x + 20) + 'px';
};
.mycard span {
position: absolute;
display: none;
z-index: 99;
}
.mycard:hover span {
display: block;
position: fixed;
overflow: hidden;
}
.imgHover a {
position: relative;
}
.imgHover span {
position: absolute;
display: none;
z-index: 99;
}
.imgHover a:hover span {
display: block;
position: fixed;
overflow: hidden;
}
<div class="imgHover mycard">
<div class="cardcost">
<p class="cardcosttext">2</p>
</div>
<div class="hscardepic">
<a style="margin-left: 1000%;vertical-align: middle;">
Doomsayer
<span id="moveHover">
<img src="Classic_Set/doomsayer.png" height="300" width="300" />
</span>
</a>
</div>
<div class="cardamount">
<p class="cardamounttext">×2</p>
</div>
</div>
If I understand what you're asking, you could use querySelectorAll to get the elements and forEach to move them:
// get the div that responds to mouse movement
const mycard = document.querySelector('.mycard');
// add a mousemove listener
mycard.addEventListener('mousemove', function(e) {
// get the DOM element with the mousemove listener from the event
const {target} = e;
// get img child elements of the target.
// (use whatever css selector you need here. doesn't have to img)
const images = target.querySelectorAll('img');
// iterate over each item...
images.forEach(image => {
// ...and do whatever you need to do with it
const x = e.clientX;
const y = e.clientY;
image.style.top = (y + 20) + 'px';
image.style.left = (x + 20) + 'px';
})
});
I'm also not entirely sure what your end-goal is, but I'll take a stab at it.
I would recommend changing moveHover to being the class instead of the ID. Then you could do something like this:
var mouseHover = null;
window.onmousemove = function (e) {
if(mouseHover != null){
var x = e.clientX;
var y = e.clientY;
mouseHover.style.top = (y+20) + 'px';
mouseHover.style.left = (x+20) + 'px';
}
};
function onHover(e){
mouseHover = e.target.querySelector('.moveHover');
}
var elements = document.getElementsByClassName('imgHover');
for(var i = 0; i < elements.length; i++){
elements[i].onmouseenter = onHover;
}
The loop runs one time to set the onmouseenter event. Sure beats moving all .moveHover elements all the time.
I'm trying to fade in/out and fix the blue div on the left when scrolled relative to the image blocks on the right.
http://www.warface.co.uk/#/testing/
pass: squared
.meta { /*This is the block I'm trying to stick/*
background: blue;
position: fixed;
width: 372px;
float: left;
z-index: 3;
right: 100%;
}
Here is the basics in JavaScript:
function controlMeta() {
var meta = document.querySelector("div.meta");
console.log(meta);
if (window.scrollY > 500) {
meta.style.display = "none";
} else {
meta.style.display = "block";
}
}
window.addEventListener("scroll", function () {
controlMeta();
})
You can get your elements scroll position with something like this:
document.getElementById("57513a9220c6475fb77061c5").getBoundingClientRect().top+window.scrollY
EDIT 1
Here is a method for associating elements with the meta box, based upon the previous:
//Load elements that affect the meta box
var meta = [];
var images = document.querySelector('.sqs-gallery').getElementsByTagName('img');
for (var i = 0; i < images.length; i++) {
meta.push({
node : images[i],
height : images[i].height,
//top is used with scroll position to determine which element we are looking at
top : images[i].getBoundingClientRect().top + window.scrollY
});
}
function controlMeta() {
meta.filter(function (el) {
//You might need to pad your filter a little
return window.scrollY < el.top && window.scrollY > el.top - el.height;
}).forEach(function (el) {
//These are the matching elements. I'm just fetching the src as an example
document.querySelector("div.meta div.body").innerHTML = el.node.src;
});
}
window.addEventListener("scroll", function () {
controlMeta();
});
Please take a look at this fiddle: http://jsfiddle.net/dhcyA/
Try clicking on a block. What I want is that when the other elements disapear, the selected block will animate/ease to his giving position instead of just jumping like it does now. Then the same animation repeats itself when clicking again on the box, but then back to place.
Maybe to keep in mind:
I'm using a reponsive design, which means those blocks can be vertical and horizontal after scaling the window.
Any redevisions on the fiddle or suggustions would be great!
Here is my solution.
On your existing markup, I added a wrapper division to calculate the position of boxes inside the wrapper. Like this
<div id="wrapper">
<div class="block">
<h2>I'm block 1</h2>
</div>
....
</div>
To maintain the fluidness of the block, I created a function to position the block on the wrapper. Here is the function for position of the blocks:
var reposition = function() {
wrapper = $("#wrapper");
console.log(wrapper.innerWidth());
pLeft = 0;
pTop = 0;
maxRowHeight = 0;
$(".block").each(function(){
if($(this).data('active')) {
$(this).data('top', pTop);
$(this).data('left', pLeft);
} else {
$(this).stop(0,0).animate({
'top' : pTop + 'px',
'left' : pLeft + 'px'
});
}
pLeft += $(this).outerWidth() + parseInt($(this).css('marginLeft'));
if($(this).height() > maxRowHeight) maxRowHeight = $(this).outerHeight() + parseInt($(this).css('marginTop')); //Find out the longest block on the row
if(pLeft + $(this).next().outerWidth() + parseInt($(this).next().css('marginLeft')) >= wrapper.innerWidth()) {
pLeft = 0;
pTop += maxRowHeight;
maxRowHeight = 0;
}
});
};
Finally, the script to toggle the block
$(".block").click(function() {
$(this).siblings().slideToggle('slow'); //Toggle other blocks
if(!$(this).data('active')){ //if the block is not active
$(this).data('left', $(this).position().left); //sets its left
$(this).data('top', $(this).position().top); // and top position
$(this).animate({ //animate at the top and bottom
top:0,
left:0
},'slow');
$(this).data('active',true);
}else{
$(this).animate({ //animate to its last known position
top:$(this).data('top'),
left:$(this).data('left')
},'slow');
$(this).data('active',false);
}
});
Demos
Demo[Full] (Resize this to see the fluidness maintained)
Demo[Full] (version showing variable heights)
Here is what this solutions gives:
Remembers the last position and gradually animate to/from this position
Block positions are calculated and animated on load and every resize
Repositioning happens on $(window).resize() thus maintaining the fluid nature of the block, despite the use of position absolute
Support variable heights
Minor change on existing markup & CSS
Also fixed two issues extended by Gaby
Accounts for each block margin independently
Recalculates the position of the element after resize
Final Update
Here is a full working solution (pretty straight forward in my opinion) with JS to set the positioning (a simple calculation) and CSS transitions for the rest..
Demo at http://jsfiddle.net/gaby/pYdKB/3/
It maintains the fluidity of float:left and works with any number of elements, and you can keep the :nth-child for the styling, and it will also work if you want to leave more than one element visible..
javascript
var wrapper = $('.wrapper'),
boxes = wrapper.children(),
boxWidth = boxes.first().outerWidth(true),
boxHeight = boxes.first().outerHeight(true);
function rePosition(){
var w = wrapper.width(),
breakat = Math.floor( w / boxWidth ); // calculate fluid layout, just like float:left
boxes
.filter(':not(.go)')
.each(function(i){
var matrixX = ((i)%breakat)+1,
matrixY = Math.ceil((i+1)/breakat);
$(this).css({
left:(matrixX-1) * boxWidth ,
top: (matrixY-1) * boxHeight
});
});
}
$('.box').click(function(){
$(this)
.siblings()
.toggleClass('go');// just add the go class, and let CSS handle the rest
rePosition(); // recalculate final positions and let CSS animate the boxes
});
$(window).resize(rePosition);
$(window).trigger('resize');
CSS
.wrapper{
position:relative;
}
.box{
width:200px;
height:100px;
position:absolute;
margin:5px;
cursor:pointer;
overflow:hidden;
text-align: center;
line-height: 100px;
-moz-transition-property: top,left,width,height;
-webkit-transition-property: top,left,width,height;
-ms-transition-property: top,left,width,height;
-o-transition-property: top,left,width,height;
transition-property: top,left,width,height;
-moz-transition-duration: 1s;
-webkit-transition-duration: 1s;
-ms-transition-duration: 1s;
-o-transition-duration: 1s;
transition-duration: 1s;
}
.go{
height:0;
width:0;
}
note: As #Athari correctly mentioned in the comments, you should include all browser prefixes for the widest support. (my initial answer only included moz / webkit and the standard)
Original Answer
You can not do it directly with your current HTML structure. The floated concept does not support it.
But if you can afford an extra wrapper, then it is no problem..
Just slide the contents of your extra wrapper element..
Put the float code on the wrapper element and use
$(document).ready(function() {
$(".block-wrapper").click(function() {
$(this).siblings().find('.block').slideToggle("slow");
});
});
Demo at http://jsfiddle.net/gaby/t8GNP/
Update #1
If you need to move the clicked element to the top left and back, then you cannot really do it with CSS.
You will need to manually position them (through JS), set CSS transitions (or jquery), and apply the new positions once you click.
Later on you might want more than one to remain visible and reposition as well..
So you might want to take a look at the great Isotope plugin which can handle this and a multitude of more situations/layouts
Here is my version:
http://jsfiddle.net/selbh/dhcyA/92/
(only javascript is changed, and it's responsive)
$(document).ready(function() {
$(".block").click(function() {
var $this = $(this);
var pos = $this.offset();
var $siblings = $(this).siblings().add(this);
var marginTop = $this.css('marginTop').replace(/[^-\d\.]/g, '');
var marginLeft = $this.css('marginLeft').replace(/[^-\d\.]/g, '');
var $clone = $this.clone();
$siblings.slideToggle("slow");
$clone.css({
position: 'absolute',
left: pos.left - marginLeft,
top: pos.top - marginTop,
'background-color': $this.css('background-color')
});
$('body').append($clone);
$this.css('opacity', 0);
$clone.animate({
'left': 0,
'top': 0
});
$clone.click(function() {
$siblings.slideToggle("slow", function() {
$clone.remove();
$this.css('opacity', 1);
});
$clone.animate({
left: pos.left - marginLeft,
top: pos.top - marginTop
});
});
});
});
I'm kind of sleepy(It's 2:30 AM here) so I leave the half done answer here to give you an idea (I did it in 30 minutes so I guess with 30 minutes more you can get something really nice)
http://jsfiddle.net/LuL2s/2/
The trick comes by the block-holder which make the ease animation and making a difference between when they appear and disappear
JS
$(document).ready(function() {
var open = true;
$(".block").click(function() {
var $this = $(this);
var count = 0;
if (open) {
$this.parent().siblings().children().slideToggle("slow", function(){
if (count++ == 2) {
$this.parent().siblings().animate({width: 'toggle', height:'toggle'});
}
});
} else {
$this.parent().siblings().animate({width: 'toggle', height:'toggle'}, function(){
if (count++ == 2) {
$this.parent().siblings().children().slideToggle("slow");
}
});
}
open = !open;
});
});
HTML
<div class="block-holder">
<div class="block">
<h2>I'm block 1</h2>
</div>
</div>
<div class="block-holder">
<div class="block">
<h2>I'm block 2</h2>
</div>
</div>
<div class="block-holder">
<div class="block">
<h2>I'm block 3</h2>
</div>
</div>
<div class="block-holder">
<div class="block">
<h2>I'm block 4</h2>
</div>
</div>
CSS
.block {
width: 100%;
height: 100%;
text-align: center;
line-height: 100px;
cursor: pointer;
}
.block-holder:nth-child(1) .block {
background: green;
}
.block-holder:nth-child(2) .block {
background: red;
}
.block-holder:nth-child(3) .block {
background: orange;
}
.block-holder:nth-child(4) .block {
background: pink;
}
.block-holder {
width: 200px;
height: 100px;
float: left;
margin: 20px;
}
Great Challenge!
New Version:
Here is a much better version as it makes the blocks stay in their rows. I added a css function so that your nth-child styles could be applied even in the rows. Even maintains same HTML Structure.
Demo: http://jsfiddle.net/MadLittleMods/fDDZB/23/
The jQuery for this new revision looks like:
$('.block').on('click', function() {
var block = $(this);
// Keep the blocks in line
makeRows($('body'));
$('.block').not(this).each(function() {
// If sibling on the same level, horizontal toggle
// We also want ignore the toggleMethod if it is shown because we might need to reassign
if (($(this).position().top == block.position().top && (($(this).data('toggle') == -1) || $(this).data('toggle') == null)) || ($(this).data('toggle') != -1 && $(this).data('toggleMethod') == 'side'))
{
$(this).data('toggleMethod', 'side');
// Hide block
if ($(this).data('toggle') == -1 || $(this).data('toggle') == null)
{
// Set properties for later use in show block
$(this).data('overflowBefore', $(this).css('overflow'));
$(this).css('overflow', 'hidden');
$(this).data('marginBefore', $(this).css('margin'));
var width = $(this).width();
$(this).animate({
width: 0,
margin: 0
}, function() {
$(this).data('toggle', width);
});
}
// Show block
else
{
$(this).css('overflow', $(this).data('overflowBefore'));
$(this).animate({
width: $(this).data('toggle'),
margin: $(this).data('marginBefore')
}, function() {
$(this).data('toggle', -1);
});
}
}
// Do a normal vertical toggle
else
{
$(this).data('toggleMethod', 'top');
$(this).slideToggle('slow');
}
});
});
// Make rows to make the blocks in line
function makeRows(container)
{
// Make rows so that the elements stay where they should
var containerWidth = container.width();
var currentRowWidth = 0;
// Add styles first so nothing gets messed up
container.children().each(function() {
var itemCSS = css($(this));
$(this).css(itemCSS);
});
// Now assemble the rows
container.children().each(function() {
var blockWidth = $(this).outerWidth() + parseInt($(this).css('margin-left')) + parseInt($(this).css('margin-right'));
if((currentRowWidth + blockWidth) < containerWidth)
{
currentRowWidth += blockWidth;
}
else
{
Array.prototype.reverse.call($(this).prevUntil('.row')).wrapAll('<div class="row"></div>');
$(this).prev().append('<div class="row_clear" style="clear: both;"></div>');
currentRowWidth = 0;
}
});
}
// Remove the rows added
function deleteRows()
{
var content = $('.row').contents()
$('.row').replaceWith(content);
$('.row_clear').remove();
}
$(window).resize(function() {
deleteRows();
});
// Functions courtesy of marknadal
// https://stackoverflow.com/a/5830517/796832
function css(a)
{
var sheets = document.styleSheets, o = {};
for(var i in sheets) {
var rules = sheets[i].rules || sheets[i].cssRules;
for(var r in rules) {
if(a.is(rules[r].selectorText)) {
o = $.extend(o, css2json(rules[r].style), css2json(a.attr('style')));
}
}
}
return o;
}
function css2json(css)
{
var s = {};
if(!css) return s;
if(css instanceof CSSStyleDeclaration) {
for(var i in css) {
if((css[i]).toLowerCase) {
s[(css[i]).toLowerCase()] = (css[css[i]]);
}
}
} else if(typeof css == "string") {
css = css.split("; ");
for (var i in css) {
var l = css[i].split(": ");
s[l[0].toLowerCase()] = (l[1]);
};
}
return s;
}
I added a makeRows and deleteRows functions so that the blocks would stay in their rows instead of getting smaller and moving into the row above. I call deleteRows whenever the window resizes so that it can maintain a responsive layout. Then if the blocks need to be toggled, I recreate the rows.
css and css2json functions are courtesy of marknadal
Old version:
I came up with a solution with .animate so that it could ease horizontally.
Here is a demo: http://jsfiddle.net/MadLittleMods/fDDZB/8/
The jQuery looks like:
$('.block').on('click', function() {
var block = $(this);
$(this).siblings().each(function() {
// If sibling on the same level, horizontal toggle
// We also want ignore the toggleMethod if it is shown because we might need to reassign
if (($(this).position().top == block.position().top && ($(this).data('toggle') == -1) || $(this).data('toggle') == null) || ($(this).data('toggle') != -1 && $(this).data('toggleMethod') == 'side'))
{
$(this).data('toggleMethod', 'side');
// Hide block
if ($(this).data('toggle') == -1 || $(this).data('toggle') == null)
{
// Set properties for later use in show block
$(this).data('overflowBefore', $(this).css('overflow'));
$(this).css('overflow', 'hidden');
$(this).data('marginBefore', $(this).css('margin'));
var width = $(this).width();
$(this).animate({
width: 0,
margin: 0
}, function() {
$(this).data('toggle', width);
});
}
// Show block
else
{
$(this).css('overflow', $(this).data('overflowBefore'));
$(this).animate({
width: $(this).data('toggle'),
margin: $(this).data('marginBefore')
}, function() {
$(this).data('toggle', -1);
});
}
}
// Do a normal vertical toggle
else
{
$(this).data('toggleMethod', 'top');
$(this).slideToggle('slow');
}
});
});
The key was to separate the blocks that were toggled with .slideToggle and .animate because you have to apply the same when they show and hide.