JavaScript slidedown without jQuery
I wish to have a similar effect to jQuery slidedown but without using jQuery or any other libary. I know it's "possible" as anything in jQuery can be done in plain JavaScript. Just harder.
I cannot use jQuery as everything has to be written in my own code without any libaries used.
Has anyone done something like this or an开发者_Go百科y effects just using plain JavaScript?
Since we are in 2014, why not use CSS transitions and just change the height property of the element? Fiddle
CSS:
.wrapper {
transition:height 1s ease-out;
height:0;
overflow:hidden;
}
HTML:
<div id="wrapper">
//content
</div>
JAVASCRIPT:
document.getElementById("wrapper").style.height = //content height +"px";
2020 EDIT (dealing with unknown height):
So we're in 2020 and it's even more obvious now we should rely on CSS effects for this kind of animations.
However, a valid point has been made against this answer - you need to specify the height of the element that you're animating in the js code, and you might not know this value in advance.
So six years later, I'm adding a couple extra lines of code to cover this case.
So if we use the same CSS and HTML as in our old 2014 example, this is the new JS. New Fiddle!
const slideDown = elem => elem.style.height = `${elem.scrollHeight}px`;
slideDown(document.getElementById("wrapper"));
Here is a nice little piece of code I wrote from scratch.
It is purely time based.
var minheight = 20;
var maxheight = 100;
var time = 1000;
var timer = null;
var toggled = false;
window.onload = function() {
var controller = document.getElementById('slide');
var slider = document.getElementById('slider');
slider.style.height = minheight + 'px'; //not so imp,just for my example
controller.onclick = function() {
clearInterval(timer);
var instanceheight = parseInt(slider.style.height); // Current height
var init = (new Date()).getTime(); //start time
var height = (toggled = !toggled) ? maxheight: minheight; //if toggled
var disp = height - parseInt(slider.style.height);
timer = setInterval(function() {
var instance = (new Date()).getTime() - init; //animating time
if(instance <= time ) { //0 -> time seconds
var pos = instanceheight + Math.floor(disp * instance / time);
slider.style.height = pos + 'px';
}else {
slider.style.height = height + 'px'; //safety side ^^
clearInterval(timer);
}
},1);
};
};
Test it here: http://jsbin.com/azewi5/5
As an improvement to @Ruben Serrate solution which misses the use case for unknown height, I've created this using CSS3 and javascript (no jQuery):
/**
* getHeight - for elements with display:none
*/
getHeight = function(el) {
var el_style = window.getComputedStyle(el),
el_display = el_style.display,
el_position = el_style.position,
el_visibility = el_style.visibility,
el_max_height = el_style.maxHeight.replace('px', '').replace('%', ''),
wanted_height = 0;
// if its not hidden we just return normal height
if(el_display !== 'none' && el_max_height !== '0') {
return el.offsetHeight;
}
// the element is hidden so:
// making the el block so we can meassure its height but still be hidden
el.style.position = 'absolute';
el.style.visibility = 'hidden';
el.style.display = 'block';
wanted_height = el.offsetHeight;
// reverting to the original values
el.style.display = el_display;
el.style.position = el_position;
el.style.visibility = el_visibility;
return wanted_height;
};
/**
* toggleSlide mimics the jQuery version of slideDown and slideUp
* all in one function comparing the max-heigth to 0
*/
toggleSlide = function(el) {
var el_max_height = 0;
if(el.getAttribute('data-max-height')) {
// we've already used this before, so everything is setup
if(el.style.maxHeight.replace('px', '').replace('%', '') === '0') {
el.style.maxHeight = el.getAttribute('data-max-height');
} else {
el.style.maxHeight = '0';
}
} else {
el_max_height = getHeight(el) + 'px';
el.style['transition'] = 'max-height 0.5s ease-in-out';
el.style.overflowY = 'hidden';
el.style.maxHeight = '0';
el.setAttribute('data-max-height', el_max_height);
el.style.display = 'block';
// we use setTimeout to modify maxHeight later than display (to we have the transition effect)
setTimeout(function() {
el.style.maxHeight = el_max_height;
}, 10);
}
}
Here is the demo: http://jsfiddle.net/pgfk2mvo/
Please let me know if you can find any improvements to this as I always try to improve my code. Happy coding ! :D
can be done in plain JavaScript. Just harder.
Actually it's not too hard. You just need to get comfortable with setTimeout()
(which is a good idea anyway since it teaches you the programming style of node.js). The most bare-bones implementation (does not have all of jQuery's features, that's left as homework for the reader):
function slideDown (element, duration, finalheight, callback) {
var s = element.style;
s.height = '0px';
var y = 0;
var framerate = 10;
var one_second = 1000;
var interval = one_second*duration/framerate;
var totalframes = one_second*duration/interval;
var heightincrement = finalheight/totalframes;
var tween = function () {
y += heightincrement;
s.height = y+'px';
if (y<finalheight) {
setTimeout(tween,interval);
}
}
tween();
}
Of course, that's not the shortest possible way to write it and you don't have to declare all those variables like one_second
etc. I just did it this way for clarity to show what's going on.
This example is also shorter and easier to understand than trying to read jQuery's source code.
Has anyone done something like this or any effects just using plain JavaScript?
Oh yeah, sure, it's the sort of thing I do for fun on weekends:
- http://slebetman.110mb.com/tank3.html (hint: units are clickable)
- http://slebetman.110mb.com/jsgames/freakout
Here's the solution to use slideDown, slideUp animation with an unknown content height element. https://jsfiddle.net/gebpjo1L/18/
It based on the CSS 3 height animation, but the animation requires a specified content height, so you need to get the height of the content via JavaScript before you expanding it.
var container = document.querySelector('div')
var button = document.querySelector('button')
button.addEventListener('click', () => {
/** Slide down. */
if(!container.classList.contains('active')) {
/** Show the container. */
container.classList.add('active')
container.style.height = "auto"
/** Get the computed height of the container. */
var height = container.clientHeight + "px"
/** Set the height of the content as 0px, */
/** so we can trigger the slide down animation. */
container.style.height = "0px"
/** Do this after the 0px has applied. */
/** It's like a delay or something. MAGIC! */
setTimeout(() => {
container.style.height = height
}, 0)
/** Slide up. */
} else {
/** Set the height as 0px to trigger the slide up animation. */
container.style.height = "0px"
/** Remove the `active` class when the animation ends. */
container.addEventListener('transitionend', () => {
container.classList.remove('active')
}, {once: true})
}
})
div {
transition: height .5s ease;
overflow : hidden;
}
div:not(.active) {
display: none;
}
<div>
I'm an unknown content height element.
I'm an unknown content height element.
I'm an unknown content height element.
I'm an unknown content height element.
I'm an unknown content height element.
I'm an unknown content height element.
I'm an unknown content height element.
I'm an unknown content height element.
I'm an unknown content height element.
I'm an unknown content height element.
I'm an unknown content height element.
I'm an unknown content height element.
</div>
<button>Slide Toggle</button>
The problem with the earlier answers is you need to know the height before you start. Many times you do not. I built a slid down the you first build a holder div, put the object to ge the slide down inside set the display of the object to block and get the height and use this for the sind. When the side is finished the holder is rmoved do the slide down when done remove it. below is the example.
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<div >
<div id="test" style="height: 150px; width: 100px; background-color: yellowgreen; display:none">block</div>
</div>
<div> </div>
<div onclick="slideUp(document.getElementById('test'));">slide Up</div>
<div> </div>
<div onclick="slideDown(document.getElementById('test'))">slide down</div>
<script>
function slideDown(obj, speed) {
var mySpeed = speed || 300;
var intervals = mySpeed / 30; // we are using 30 ms intervals
alert('intervals = ' + intervals);
var holder = document.createElement('div');//
var parent = obj.parentNode;
holder.setAttribute('style', 'height: 0px; overflow:hidden');
parent.insertBefore(holder, obj);
parent.removeChild(obj);
holder.appendChild(obj);
obj.style.display = obj.getAttribute("data-original-display") || "";
var height = obj.offsetHeight;
var sepHeight = height / intervals;
// alert(sepHeight)
var timer = setInterval(function() {
var holderHeight = holder.offsetHeight;
if (holderHeight + sepHeight < height) {
holder.style.height = (holderHeight + sepHeight) + 'px';
} else {
// clean up
holder.removeChild(obj);
parent.insertBefore(obj, holder);
parent.removeChild(holder);
clearInterval(timer);
}
}, 30);
}
function slideUp(obj, speed) {
var mySpeed = speed || 300;
var intervals = mySpeed / 30; // we are using 30 ms intervals
var height = obj.offsetHeight;
var holder = document.createElement('div');//
var parent = obj.parentNode;
holder.setAttribute('style', 'height: ' + height + 'px; overflow:hidden');
parent.insertBefore(holder, obj);
parent.removeChild(obj);
holder.appendChild(obj);
var originalDisplay = (obj.style.display !== 'none') ? obj.style.display : '';
obj.setAttribute("data-original-display", originalDisplay);
var sepHeight = height / intervals;
// alert(sepHeight)
var timer = setInterval(function() {
var holderHeight = holder.offsetHeight;
console.log(holderHeight);
if (holderHeight - sepHeight > 0) {
holder.style.height = (holderHeight - sepHeight) + 'px';
} else {
// clean up
obj.style.display = 'none';
holder.removeChild(obj);
parent.insertBefore(obj, holder);
parent.removeChild(holder);
clearInterval(timer);
}
}
, 30);
}
</script>
</body>
</html>
After tinkering a little with Symba's answer I came up with this to take into account padding and borders:
toggleSlide = function (el) {
var el_max_height = 0;
if (el.getAttribute('data-max-height')) {
if (el.style.maxHeight.replace('px', '').replace('%', '') === '0') {
el.style.maxHeight = el.getAttribute('data-max-height');
el.style.paddingTop = el.getAttribute('data-pad-top');
el.style.paddingBottom = el.getAttribute('data-pad-bottom');
el.style.borderTop = el.getAttribute('data-border-top');
el.style.borderBottom = el.getAttribute('data-border-bottom');
} else {
el.style.maxHeight = '0';
el.style.paddingTop = '0';
el.style.paddingBottom = '0';
el.style.borderBottom = '0';
el.style.borderTop = '0';
}
} else {
el_max_height = getHeight(el) + 'px';
el.style['transition-property'] = 'max-height, padding-top, padding-bottom, border-bottom, border-top';
el.style['transition-duration'] = '0.5s';
el.style['transition-timing-function'] = 'ease-in-out';
el.style.overflowY = 'hidden';
el.style.maxHeight = '0';
el.setAttribute('data-max-height', el_max_height);
el.setAttribute('data-pad-top', el.style.paddingTop);
el.setAttribute('data-pad-bottom', el.style.paddingBottom);
el.setAttribute('data-border-top', el.style.borderTop);
el.setAttribute('data-border-bottom', el.style.borderBottom);
el.style.display = 'block';
setTimeout(function () { el.style.maxHeight = el_max_height; }, 10);
}
}
There's a bit of flicker at top border when expanding transition begins and I don't know (yet) how to fix that.
I didn't modify Symba's getHeight function; for that see his answer.
this pure js solution doesn't require knowing the the max-height beforehand, and uses css-transitions.
it will set max-height to 2x browser window's height during transition, and then the remove max-height after the transition is done.
full slideDown, slideUp demo @ https://kaizhu256.github.io/node-swagger-lite/build..alpha..travis-ci.org/app/index.html
css
.swggAnimateSlide {
overflow-y: hidden;
transition: all 500ms linear;
}
.swggAnimateSlideUp {
border-bottom: 0 !important;
border-top: 0 !important;
margin-bottom: 0 !important;
margin-top: 0 !important;
max-height: 0 !important;
padding-bottom: 0 !important;
padding-top: 0 !important;
}
js
domAnimateSlideDown = function (element) {
/*
* this function will slideDown the dom-element
*/
if (element.style.maxHeight || element.style.display !== 'none') {
return;
}
element.classList.add('swggAnimateSlideUp');
element.classList.add('swggAnimateSlide');
element.style.display = '';
setTimeout(function () {
element.style.maxHeight = 2 * window.innerHeight + 'px';
element.classList.remove('swggAnimateSlideUp');
}, 50);
setTimeout(function () {
element.style.maxHeight = '';
element.classList.remove('swggAnimateSlide');
}, 500);
};
domAnimateSlideUp = function (element) {
/*
* this function will slideUp the dom-element
*/
if (element.style.maxHeight || element.style.display === 'none') {
return;
}
element.style.maxHeight = 2 * window.innerHeight + 'px';
element.classList.add('swggAnimateSlide');
setTimeout(function () {
element.classList.add('swggAnimateSlideUp');
element.style.maxHeight = '0px';
}, 50);
setTimeout(function () {
element.style.display = 'none';
element.style.maxHeight = '';
element.classList.remove('swggAnimateSlide');
element.classList.remove('swggAnimateSlideUp');
}, 500);
};
I spent a lot of time to develop these functions and make them the most similar to JQuery's ones, I think they are some details to be optimized in the morph function mainly on length
function morph( element, options, animationTime, callback ) {
// options is an array with the same structural of Properties and DiffValues and which contains the properties values we want the animation make
var ComputedElementStyle = window.getComputedStyle(element,null);
var AttrElementStyle = element.style;
var Properties = { // the actuals computed properties
width: parseInt( ComputedElementStyle.getPropertyValue("width")),
height: parseInt( ComputedElementStyle.getPropertyValue("height")),
padding: {
top: parseInt(ComputedElementStyle.getPropertyValue("padding-top")),
right: parseInt(ComputedElementStyle.getPropertyValue("padding-right")),
bot: parseInt(ComputedElementStyle.getPropertyValue("padding-bottom")),
left: parseInt(ComputedElementStyle.getPropertyValue("padding-left"))
},
margin:{
top: parseInt(ComputedElementStyle.getPropertyValue("margin-top")),
right: parseInt(ComputedElementStyle.getPropertyValue("margin-right")),
bot: parseInt(ComputedElementStyle.getPropertyValue("margin-bottom")),
left: parseInt(ComputedElementStyle.getPropertyValue("margin-left"))
}
};
var DiffValues = { // the differences between actual properties values and values we want to
width: (options['width']!=null) ? (options['width'] - Properties['width']) : 0,
height: (options['height']!=null) ? (options['height'] - Properties['height']) : 0,
padding: {
top: (options['padding']&&options['padding']['top']!=null) ? options['padding']['top'] - Properties['padding']['top'] : 0,
right: (options['padding']&&options['padding']['right']!=null) ? options['padding']['right'] - Properties['padding']['right'] : 0,
bot: (options['padding']&&options['padding']['bot']!=null) ? options['padding']['bot'] - Properties['padding']['bot'] : 0,
left: (options['padding']&&options['padding']['left']!=null) ? options['padding']['left'] - Properties['padding']['left'] : 0
},
margin:{
top: (options['margin']&&options['margin']['top']!=null) ? options['margin']['top'] - Properties['margin']['top'] : 0,
right: (options['margin']&&options['margin']['right']!=null) ? options['margin']['right'] - Properties['margin']['right'] : 0,
bot: (options['margin']&&options['margin']['bot']!=null) ? options['margin']['bot'] - Properties['margin']['bot'] : 0,
left: (options['margin']&&options['margin']['left']!=null) ? options['margin']['left'] - Properties['margin']['left'] : 0
}
};
var beginTime = new Date().getTime(); // time at begining of animation
animationTime = (animationTime!=null) ? animationTime : 250;
AttrElementStyle.overflow = "hidden"; // disable the potentials scrollbars
var sinceBeginTime; // time since the begining
var progressFactor; // coeficient that correspond to the advancement of the animation
timer = setInterval(function() { // begin of the animation
sinceBeginTime = new Date().getTime() - beginTime;
if( sinceBeginTime < animationTime ) {
progressFactor = sinceBeginTime / animationTime;
AttrElementStyle.width=(Properties['width'] + DiffValues['width'] * progressFactor) +"px";
AttrElementStyle.height=(Properties['height'] + DiffValues['height'] * progressFactor) +"px";
AttrElementStyle.padding=
(Properties['padding']['top'] + DiffValues['padding']['top'] * progressFactor) +"px "+
(Properties['padding']['right'] + DiffValues['padding']['right'] * progressFactor) +"px "+
(Properties['padding']['bot'] + DiffValues['padding']['bot'] * progressFactor) +"px "+
(Properties['padding']['left'] + DiffValues['padding']['left'] * progressFactor) +"px";
AttrElementStyle.margin=
(Properties['margin']['top'] + DiffValues['margin']['top'] * progressFactor) +"px "+
(Properties['margin']['right'] + DiffValues['margin']['right'] * progressFactor) +"px "+
(Properties['margin']['bot'] + DiffValues['margin']['bot'] * progressFactor) +"px "+
(Properties['margin']['left'] + DiffValues['margin']['left'] * progressFactor) +"px";
}else {
AttrElementStyle.width=options['width'] +"px";
AttrElementStyle.height=options['height'] +"px";
AttrElementStyle.padding=
(Properties['padding']['top'] + DiffValues['padding']['top']) +"px "+
(Properties['padding']['right'] + DiffValues['padding']['right']) +"px "+
(Properties['padding']['bot'] + DiffValues['padding']['bot']) +"px "+
(Properties['padding']['left'] + DiffValues['padding']['left']) +"px";
AttrElementStyle.margin=
(Properties['margin']['top'] + DiffValues['margin']['top']) +"px "+
(Properties['margin']['right'] + DiffValues['margin']['right']) +"px "+
(Properties['margin']['bot'] + DiffValues['margin']['bot']) +"px "+
(Properties['margin']['left'] + DiffValues['margin']['left']) +"px";
clearInterval( timer ); // end of the animation
if( callback!=null ) // if there is a CALLBACK then call it
callback(Properties);
}
},15);
}
function slideUp( element, animationTime , callback) {
morph( element, {
height:0,
padding:{
top:0,
bot:0
},
margin:{
top:0,
bot:0
} }, animationTime, function(Properties) {
// at the end of the slideUp we display: none the element and clean the other properties from style attribute
var AttrElementStyle = element.style;
AttrElementStyle.width="";
AttrElementStyle.height="";
AttrElementStyle.padding="";
AttrElementStyle.margin="";
element.style.display = 'none';
if(callback)
callback();
});
}
function slideDown( element, animationTime , callback) {
var AttrElementStyle = element.style;
var ComputedElementStyle = window.getComputedStyle(element,null);
AttrElementStyle.display="block";
var options = { // the computed properties when element is displayed
width: parseInt( ComputedElementStyle.getPropertyValue("width")),
height: parseInt( ComputedElementStyle.getPropertyValue("height")),
padding: {
top: parseInt(ComputedElementStyle.getPropertyValue("padding-top")),
bot: parseInt(ComputedElementStyle.getPropertyValue("padding-bottom"))
},
margin:{
top: parseInt(ComputedElementStyle.getPropertyValue("margin-top")),
bot: parseInt(ComputedElementStyle.getPropertyValue("margin-bottom"))
}
};
// after getting the actuals properties values of the element we flatten it
AttrElementStyle.height="0";
AttrElementStyle.paddingTop="0";
AttrElementStyle.paddingBottom="0";
AttrElementStyle.marginTop="0";
AttrElementStyle.marginBottom="0";
morph( element, options , animationTime, function() { // morph the element from flat to the options properties that are right
// at the end of slideDown we clean up the style but keep the display: block if the element is display: none in the stylesheet
AttrElementStyle.width="";
AttrElementStyle.height="";
AttrElementStyle.padding="";
AttrElementStyle.margin="";
element.style.display = 'block';
if(callback) // if there is a CALLBACK then call it (we are in the morph() callback)
callback();
})
}
p{
width:50%;
height:auto;
padding:40px 0 10px;
margin: 10px 0 40px;
border:2px solid red
}
div{
border:1px solid blue;
padding:10px
}
<div>
<p id='p'>loiloilozoifboiygdouhbodihfgsd</br>rihgdggisbifghbsoifnsf</br>giodsbgsigfbsjgsgs</p>
</div>
<button onClick="slideUp( document.getElementById('p') ,500)">testUp</button>
<button onClick="slideDown( document.getElementById('p') ,500)">testDown</button>
<button id='btn1' onClick="morph( document.getElementById('btn1'),{ width: 120,height:130,padding:{top:70,left:50},margin:{right:40}} ,1000)">morphIt</button>
<button onClick="document.getElementById('btn1').style=''">reset btn1</button>
Make it shorter:
<button onclick="slide()"></button>
<div id="box"></div>
JS:
slide(){document.getElementById("box").classList.toggle('hide');}
#box {
overflow: hidden;
transition: all .5s ease-in-out;
-webkit-transition: all 0.5s ease-in-out;
-moz-transition: all 0.5s ease-in-out;
-ms-transition: all 0.5s ease-in-out;
-o-transition: all 0.5s ease-in-out;
}
.hide {
height: 0px !important;
}
Building on Yami Odymels answer here is a version that takes top- and bottom-padding into account, less relies on values set in css beforehand and also removes inline-styles after finishing the animation which prevents problems with viewport-resizes.
const container = document.querySelector('.the-wrap')
const button = document.querySelector('.the-button')
button.addEventListener('click', function() {
// slide down
if(!container.classList.contains('active')) {
// show/remove display: none;
container.classList.add('active')
// get current height
const height = container.clientHeight + "px"
// remove height to 0
container.style.height = 0;
container.style.paddingTop = 0;
container.style.paddingBottom = 0;
container.style.overflow = "hidden";
// this triggers applying the height and padding-values; does not show up on screen
setTimeout(() => {
// register removing properties after end of transition
container.addEventListener('transitionend', () => {
container.style.height = "";
container.style.transition = "";
container.style.overflow = "";
}, {once: true})
// set transition
container.style.transition = "height 0.5s ease, padding-top 0.5s ease, padding-bottom 0.5s ease";
// set values to trigger transition
container.style.height = height;
container.style.paddingTop = "";
container.style.paddingBottom = "";
}, 0);
}
// slide up
else {
// explicitly set height, transitioning to auto-values is not included in spec
container.style.height = container.clientHeight + "px";
container.style.overflow = "hidden";
container.style.transition = "height 0.5s ease, padding-top 0.5s ease, padding-bottom 0.5s ease";
// again wait
setTimeout(() => {
// register cleanup
container.addEventListener('transitionend', () => {
container.classList.remove('active')
container.style.height = "";
container.style.transition = "";
container.style.overflow = "";
container.style.paddingTop = "";
container.style.paddingBottom = "";
}, {once: true});
// set to visually hidden, thus triggering transition
container.style.height = 0;
container.style.paddingTop = 0;
container.style.paddingBottom = 0;
}, 0);
}
});
*,
*:before,
*:after {
box-sizing: border-box;
}
.the-wrap {
outline: 2px solid crimson;
margin-top: 50px;
padding: 20px;
}
.the-wrap:not(.active) {
display: none;
}
<button class="the-button">toggle</button>
<div class="the-wrap">
<h1>Headline</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</div>
Have a look at the jQuery source for that effect (slideDown
calls show
).
精彩评论