Using the flexbox justify-content property, elements can be distributed evenly in their container. However, I want to animate their positions when a new element is inserted or an existing one is removed.
I only managed to animate the height of the elements so far. However, there is a jump at the end of the animation since the gaps around the removed element that got animated to height: 0 vanish. Analogously, when inserting an element there is a jump at the beginning of the animation.
Is it possible to make an animation from end to end with justify-content? Here is an example to play with. Using CSS transition is preferred.
The main problem is that the behavior you are getting is the expected one.
In the very same instant that card.remove() is executed the flexbox justify-content property need to adjust the gaps around the removed element (as you said). And, as Paulie D has pointed out, there is nothing to animate about.
The only solution I can think about is to skip the flex thing and use javascript to create the necessary gaps among the card elements.
Here I leave the snippet:
var animation_time = 500;
var column_height = $('.column').height();
var cards_height = $('.card').height();
var cards_number;
var cards_total_height;
var space_to_be_distributed;
var placeholder_height;
function updateSizes(cards_number)
{
cards_total_height = cards_number * cards_height;
space_to_be_distributed = column_height - cards_total_height;
placeholder_height = space_to_be_distributed / (cards_number + 1);
}
updateSizes($('.card').length);
$('.placeholder').height(placeholder_height);
$(document).on('click', '.card', function () {
var card = $(this);
card.animate({height: 0, opacity: 0}, animation_time, function () {
card.remove();
});
updateSizes($('.card').length - 1);
var placeholder = card.next();
$('.placeholder').not(placeholder).animate({height: placeholder_height}, animation_time);
placeholder.animate({height: 0}, animation_time, function () {
placeholder.remove();
updateSizes($('.card').length);
$('.placeholder').animate({height: placeholder_height}, animation_time);
});
});
$('a').click(function () {
var card = $('<div class="card">');
card.css({opacity: 0, height: 0})
.appendTo('.column')
.animate({height: 25, opacity: 1}, animation_time);
var placeholder = $('<div class="placeholder">');
placeholder.css({height: 0})
.appendTo('.column');
updateSizes($('.card').length);
$('.placeholder').animate({height: placeholder_height}, animation_time);
});
body, html, .column {
height: 100%;
margin: 0;
}
.column {
float: left;
width: 100px;
background: navy;
overflow: hidden;
}
.card {
height: 25px;
width: 100px;
background: grey;
}
.placeholder {
height: 25px;
width: 100px;
}
<html>
<head>
<script src="http://code.jquery.com/jquery-2.1.3.min.js"></script>
</head>
<body>
<div class="column">
<div class="placeholder"></div>
<div class="card"></div>
<div class="placeholder"></div>
<div class="card"></div>
<div class="placeholder"></div>
<div class="card"></div>
<div class="placeholder"></div>
</div>
Add card
</body>
</html>
Hope it helps!
EDIT - I made the following changes in the code:
I change the fiddle for a SO snippet.
I forced an update of elements size at the end of the animation (in case you click to remove an element before the last one has been completely removed)
I change the size of the elementes to adapt it to the (small) SO snippet window.
Related
I've tried searching to see if there's already a question about this but can't find anything - so apologies if this is in fact a duplicate!
I've seen on some websites a feature where, when scrolling, the scroll stop point is forced to stop at a specific element rather than just wherever the user actually stopped scrolling.
I imagine this can be achieved via jQuery, but can't seem to find any documentation or help articles about it.
So, here's some example HTML...
<div id="one" class="block"></div>
<div id="two" class="block"></div>
<div id="three" class="block"></div>
With this as the CSS...
#one {
background: red;
}
#two {
background: green;
}
#three {
background: yellow;
}
.block {
width: 200px;
height: 100vh;
}
And what I'm looking to achieve is that when the user scrolls their browser from div 'one' to div 'two', once they've started scrolling over div 'two' and they then stop scrolling the browser automatically jumps them so that they see div 'two' in full, rather than a bit of the bottom of div 'one' and then most of div 'two' - I've definitely seen it done before but no idea how!
I hope this makes sense, and thanks in advance for any help or insight anyone can offer...
I don't remember too well, but I guess there are many ways to achieve what you want. One thing that came to my mind is to wrap around your divs and make a separate hidden div with full height. I did this adhoc solution below:
Once scroll approaches a threshold, I move to the div I should be looking at and vice versa. Here is a working solution FIDDLE:
HTML
<div id="phantom"></div>
<div id="wrapper">
<div id="one" class="block"></div>
<div id="two" class="block"></div>
<div id="three" class="block"></div>
</div>
CSS
#one {
background: red;
}
#two {
background: green;
}
#three {
background: yellow;
}
.block {
width: 200px;
height: 100vh;
}
#wrapper {
overflow:hidden;
position:fixed;
top:0px;
}
#phantom {
visibility:hidden;
}
JS
!function(){
//the array of divs
var divs = Array.prototype.slice.call(document.getElementsByClassName("block")), count = divs.length,
wrapper = document.getElementById("wrapper"),
phantom = document.getElementById("phantom"),
//the speed of scroll
scrollStep = 5,
//total length of phantom div
totalLength = Array.prototype.slice.call(wrapper.children).reduce(function(ac,d,i,a){return ac += d.clientHeight},0),
//store the animation frame here
currentFrame;
//wrapper is overflow hidden
wrapper.style.height = totalLength/count + "px";
//phantom has full height
phantom.style.height = totalLength + "px";
//add listener for scroll
window.addEventListener("scroll",function(){
//throttle the function
if(this._busy){return};
this._busy = true;
var that = this;
window.requestAnimationFrame(function(){
that._busy = false;
var heightOfDocument = Math.max(document.documentElement.scrollHeight,document.body.scrollHeight),
totalScroll = Math.max(document.body.scrollTop,document.documentElement.scrollTop),
//which element should we look at?
whichElement = Math.round(totalScroll/heightOfDocument*count);
//if we are already around, don't do anything
if(divs[whichElement]._current){
return;
} else {
//cancel the last animation if any and start a new one
window.cancelAnimationFrame(currentFrame);
divs.forEach(function(d,i){delete d._current});
moveTo(divs[whichElement]);
}
});
},false);
//helper function to linearly move to elements
function moveTo(node){
node._current = true;
var top = node.offsetTop,
current = node.parentNode.scrollTop,
distance = top - current,
step = distance < 0 ? -scrollStep : scrollStep;
if(Math.abs(distance) < scrollStep){
node.parentNode.scrollTop = top;
return;
} else {
node.parentNode.scrollTop += step;
}
//store the current frame
currentFrame = window.requestAnimationFrame(function(){
moveTo(node);
});
}
}();
You obviously need to attach 'resize' event to update the values of the totalLength and set the correct new length on wrapper and phantom. You can also implement a easing function to modify the scrolling behavior to your taste. I leave them to you as homework.
I was wondering if there was a way to have a centered item shift smoothly when its width changes?
In my case, I have a piece of text on the left that stays the same, and the piece of text on the right will change depending on what page you are on.
<div id="title-container">
<h1 class="inline-header">example.</h1>
<h1 id="title-category" class="inline-header">start</h1>
</div>
The total width of this will change as a result, and it will shift abruptly.
Here is a jsfiddle demonstrating the problem.
https://jsfiddle.net/sm3j26aa/3/
I've currently worked around it by just fixing the left side using relative positioning and translates, but if I can get the smooth transition, I would rather do that.
Thanks for any help!
Instead of fading just the right portion in and out, you'll need to fade the entire line.
Also, there is no need for individual functions for each word change. Just have one function that accepts the new word as a parameter.
Lastly, don't use inline HTML event attributes to set up event handlers. It:
creates spaghetti code that is more difficult to read
creates anonymous wrapper functions that alter the this binding
within the function
doesn't follow W3C DOM Even Standards
Instead set up your event handlers in JavaScript.
var $titleContainer = $('#title-container');
var $titleCategory = $('#title-category');
$("button").click(function(){ change(this.textContent); })
function change(text) {
$titleContainer.fadeOut(300, function() {
$titleCategory.text(text);
$titleContainer.fadeIn(600);
})
}
#title-container, #button-container { text-align: center; }
.inline-header { display: inline-block; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="title-container">
<h1 class="inline-header left">example.</h1>
<h1 id="title-category" class="inline-header">start</h1>
</div>
<div id="button-container">
<button>Sample</button>
<button>Hello</button>
<button>SampleX2</button>
</div>
var $titleCategory = $('#title-category');
function changeToSample() {
$titleCategory.fadeOut(300, function() {
$titleCategory.text('sample');
$titleCategory.fadeIn(600);
document.getElementById('title-container').style.marginLeft = `calc(50% - 14em/2)`;
})
}
function changeToHello() {
$titleCategory.fadeOut(300, function() {
$titleCategory.text('hello');
$titleCategory.fadeIn(600);
document.getElementById('title-container').style.marginLeft = `calc(50% - 12em/2)`;
})
}
function changeToDoubleSample() {
$titleCategory.fadeOut(300, function() {
$titleCategory.text('samplesample');
$titleCategory.fadeIn(600);
document.getElementById('title-container').style.marginLeft = `calc(50% - 20em/2)`;
})
}
#title-container {
margin-left: calc(50% - 12em/2);
transition: .2s;
}
#button-container {
text-align: center;
}
.inline-header {
display: inline-block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="title-container">
<h1 class="inline-header">example.</h1>
<h1 id="title-category" class="inline-header">start</h1>
</div>
<div id="button-container">
<button onclick="changeToSample();">Sample</button>
<button onclick="changeToHello();">Hello</button>
<button onclick="changeToDoubleSample();">SampleX2</button>
</div>
I been trying to build an image slider from scratch that will slide automatically but then go to a certain slide when the corresponding dot at the bottom is clicked.
When I isolate and test each of my event delegation click functions individually, they work great to get the different photos to slide back and forth. But when all functions are together only the 3rd function works. Can you help?
To demonstrate I made a simple mock-up slider here which includes the following JavaScript/jQuery:
'use strict';
$(document).ready(function () {
//DOM cache
var $slider = $('section');
var $slideContainer = $slider.find('ul');
var $windowWidth = $slider.css('width');
var $windowWidthUnitless = $slider.width();
var $dot1 = $slider.find('#dot-1');
var $dot2 = $slider.find('#dot-2');
var $dot3 = $slider.find('#dot-3');
//config
var width = $windowWidth;
var doubleWidth = $windowWidthUnitless * 2;
$($slider).on('click',$dot1,function() {
$slideContainer.css('margin-left', 0);
$dot1.addClass('filled');
$dot2.removeClass('filled');
$dot3.removeClass('filled');
})
$($slider).on('click',$dot2,function() {
$slideContainer.css('margin-left', '-' +width);
$dot1.removeClass('filled');
$dot2.addClass('filled');
$dot3.removeClass('filled');
})
$($slider).on('click',$dot3,function() {
$slideContainer.css('margin-left', "-" +doubleWidth +"px");
$dot1.removeClass('filled');
$dot2.removeClass('filled');
$dot3.addClass('filled');
})
});
Acting on this html:
<section>
<ul>
<li>
<div id="slide-1"></div>
</li>
<li>
<div id="slide-2"></div>
</li>
<li>
<div id="slide-3"></div>
</li>
<li>
<div id="slide-1"></div>
</li>
</ul>
<div class="selected" id="dot-1"></div>
<div id="dot-2"></div>
<div id="dot-3"></div>
and CSS
* {
margin: 0;
padding: 0;
} /* just my default */
section {
overflow: hidden;
width:400px;
height:400px;
}
section ul {
width:1600px;
}
section li {
list-style-type: none;
display: inline;
float: left;
}
[id*="slide-"] {
height: 400px;
width: 400px;
}
[id*="dot-"] {
float:left;
height: 40px;
width: 40px;
border-radius: 20px;
position: relative;
bottom: 45px;
}
(if this helps, here's a more exact but still simplified version of my slider, that actually slides automatically as well and is meant to stop and go to a certain slide when a dot is clicked. Again, it only goes to the last slide.)
In each of the $($slider).on('click',$dotNumber,function() {...} lines, replace $dotNumber with '#dot-number'.
Example: $($slider).on('click','#dot-1',function() {...}
I don't know if there's a particular way to pass a jQuery object to specify a child element to target, but I do know that adding the CSS selector as the second parameter will do it. According to the man page, the second parameter (if it's not the callback) is a string.
I am rendering a series of images on the fly into a container. At the end I want a single image centered and the other images to the left and right of it.
My problem is with centering the image. I made a full fiddle with my entire code which is ironically working as I expect it to be. However when testing it I find that
centered.width()
Returns 0 instead of returning the width of the image that should be centered.
Here centered is an image-tag I previously created on the fly.
What confuses me most is how it works in the fiddle but not when I test it opening the website locally, having exactly the same code in there as in the fiddle.
Here goes the entire page I currently have.
var ashe = JSON.parse('{"id":22,"key":"Ashe","name":"Ashe","title":"the Frost Archer","skins":[{"id":22000,"name":"default","num":0},{"id":22001,"name":"Freljord Ashe","num":1},{"id":22002,"name":"Sherwood Forest Ashe","num":2},{"id":22003,"name":"Woad Ashe","num":3},{"id":22004,"name":"Queen Ashe","num":4},{"id":22005,"name":"Amethyst Ashe","num":5},{"id":22006,"name":"Heartseeker Ashe","num":6},{"id":22007,"name":"Marauder Ashe","num":7}]}');
var currentCha = ashe;
function displaySkins(cha) {
//Clear the display.
var $skinDisplay = $('#skinDisplay');
var $skinSpinner = $('#skinSpinner');
$skinDisplay.html('');
$skinSpinner.html('');
currentCha = cha;
//Add small perviews to spinner
cha.skins.forEach(function(skin) {
var img = $('<img class="small-preview" src="http://ddragon.leagueoflegends.com/cdn/img/champion/loading/' + cha.key + '_' + skin.num + '.jpg">');
$skinSpinner.append(img);
skin.img = img;
})
spinTo(0);
}
function spinTo(index) {
centered = currentCha.skins[index].img;
var left = $('#skinSpinner').width() / 2 - centered.width();
console.log(centered.width());
centered.css('left', left);
}
displaySkins(ashe);
#skinDisplay {
width: 100%;
height: 100%;
}
#skinSpinner {
width: 500px;
height: 200px;
border: 1px solid black;
perspective: 500px;
}
#skinSpinner .small-preview {
position: absolute;
display: block;
height: 100%;
width: auto;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="overlay">
<div id="skinDisplay">
</div>
<div id="skinSpinner">
</div>
</div>
bind a load checker to spinTo function:
function spinTo(index) {
centered = currentCha.skins[index].img;
$(centered).bind('load', function(){
var left = $('#skinSpinner').width() / 2 - centered.width();
console.log(centered.width());
centered.css('left', left);
});
}
Seems like you are accessing your DOM before loading it.
$( document ).ready(function(){ displaySkins(ashe); });
This will defer execution of your script until the DOM is loaded.
It works in the fiddle as they put include the script in the end of the DOM. So DOM is loaded first and the script is included and finally executed. You do it the other way round (which is also fine) which obviously does not work
I'm new and have I think very simple problem to solve.
I have 4 buttons to show/hide each panel. What should I do to prevent child divs from moving to te left while hiding some div?
I prefer them to stay at the initial position.
This is my code:
HTML:
<button class="panel-button" data-panel="panel1">1</button>
<button class="panel-button" data-panel="panel2">2</button>
<button class="panel-button" data-panel="panel3">3</button>
<button class="panel-button" data-panel="panel4">4</button>
<div class="wrapper">
<div id="panel1">1</div>
<div id="panel2">2</div>
<div id="panel3">3</div>
<div id="panel4">4</div>
</div>
JS:
$(function() {
$('.panel-button').on('click',function(){
var panelId = $(this).data('panel');// attr('data-panel')
$('#'+panelId).toggle();
});
});
CSS:
.wrapper {
overflow: hidden;
width: 420px;
}
.wrapper > div {
width: 100px;
height: 100px;
background: green;
float: left;
margin-left: 5px;
margin-top: 10px
}
Apply css rule opacity = 0; to the div, instead of hiding it.
Like this:
$('.panel-button').on('click',function(){
var pnl = $('#' + $(this).data('panel'));
pnl.css('opacity', pnl.css('opacity') == '0' ? '1' : '0');
});
Solution for clickability issue:
$('.panel-button').on('click',function(){
var pnl = $('#' + $(this).data('panel'));
if(pnl.is(':visible'))
$('<div></div>').appendTo(pnl).width(pnl.width());
else
pnl.next().remove();
pnl.toggle();
});
But still you can use another approach
You can use the visibility property in CSS to achieve this as shown in the below Fiddle link : link
JS Snippet:
$(function() {
$('.panel-button').on('click',function(){
var panelId = $(this).data('panel');// attr('data-panel')
console.log($('#'+panelId).css('visibility'));
if($('#'+panelId).css('visibility') === 'hidden') {
$('#'+panelId).css('visibility','visible');
}
else {
$('#'+panelId).css('visibility','hidden');
}
});
});
The CSS visibility is designed to keep the space a DOM object occupies, but not actually rendering it. Opacity changes its appearance, but not its behavior (eg. still clickable).
So instead of .toggle(), combine visibility with jQuery's .toggleClass():
jsFiddle solution
$(function() {
$('.panel-button').on('click',function(){
var panelId = $(this).data('panel');// attr('data-panel')
$('#'+panelId).toggleClass('hideMe');
});
});