help me out here with why my CSS transition isn't working.
I'm trying to animate a height change to a <div> element, either growing it or shrinking it, through a Javascript function. My markup is this:
<div class="post series expandcollapse" id="id1" style="height: 32px;">
<p class="expandcollapse_link" id="expandcollapselink1" name="expandcollapselink1" onclick="toggleCollapsedItem('1');"><img id="expandcollapseimage1" name="expandcollapseimage1" class="expandcollapse" src="images/icons/expandcollapse.png" alt=""></p>
<h2 onclick="toggleCollapsedItem('1');">Some random heading here</h2>
<p>Some random text here</p>
</div>
The CSS attached to the <div>, <h2>, and <img> are:
div.expandcollapse { border-bottom-left-radius: 20px; border-bottom-right-radius: 20px; height: 32px; transition: all 5s ease-in; overflow: hidden; }
div.expandcollapse h2 { cursor: pointer; }
div.expandcollapse img.expandcollapse { transition: all .5s; cursor: pointer; }
div.expandcollapse p.expandcollapse_link { float: right; margin-top: -2px; }
When the user clicks on this, it launches this Javascript function:
function toggleCollapsedItem(clickedItem, forceOpen = false) {
var collapsedItem = document.getElementById('id' + clickedItem);
var collapsedLink = document.getElementById('expandcollapselink' + clickedItem);
var collapsedImage = document.getElementById('expandcollapseimage' + clickedItem);
if(typeof collapsedItem.originalHeight === 'undefined') {
Object.defineProperty(collapsedItem, 'originalHeight', { value: collapsedItem.style.height, writable: false });
}
if((collapsedItem.style.height === collapsedItem.originalHeight) || (forceOpen === true)) {
collapsedItem.style.height = 'auto';
collapsedImage.style.transform = 'rotate(45deg)';
} else {
collapsedItem.style.height = collapsedItem.originalHeight;
collapsedImage.style.transform = '';
}
}
The image rotates gradually (according to the transition) exactly as it should, but the height doesn't care about the transition. Can anyone spot my problem here? I've been up and down Google, Stackoverflow for similar questions, all over the place, and I can't get the height to change gradually.
Thanks, everyone.
You cannot transition to or from auto values, only between defined values.
I'm assuming the element can be of varying height. If you're already using Javascript, you can transition from 32px to the element's offsetHeight.
So instead
collapsedItem.style.height = 'auto';
you'd do
collapsedItem.style.height = collapsedItem.offsetHeight+'px';
Related
I am trying place an HTML element next to the end of the first line of a heading.
For a better understanding, I took screenshots of what it looks like for now and what it should look like.
What it looks like:
What it should look like:
As you can see, I used the ::first-line pseudo-selector to change the background of the first line of the title in red.
So far I have tried to add a ::after to the ::first-line but there are two issues. First, I did not manage to display a text content in the ::after. Second, you can't add html content to both ::before and ::after so it was pointless.
You should know that the "sunshines" are not an image but an actual html element :
<div className={css.container}>
<div className={css.shine1}></div>
<div className={css.shine2}></div>
<div className={css.shine3}></div>
</div>
.container {
position: absolute;
display: inline;
top: 20px;
right: 5px;
div {
position: absolute;
height: 5px;
border-radius: 5px;
background-color: $mainColor;
transform-origin: bottom left;
}
.shine1 {
width: 26px;
transform: rotate(-95deg) translate(20px, 5px);
}
.shine2 {
width: 32px;
transform: rotate(-45deg) translate(20px);
}
.shine3 {
width: 26px;
transform: rotate(0deg) translate(15px);
}
}
This is JSX with scss modules. But basically the same syntax as HTML/CSS.
Do you have any idea on how to do it, I guess it is possible since the css is capable to know where the end of the line is since I can change the background.
I have figured it out.
Although my solution is very "hacky", it works fine in any test case I thought about.
My solution is to create a hidden replica of the first line on top of the title with the same css properties. Once that is done I just get the width of the replica and use that value to define the left property of my sunshines.
This is the function I use to get the first line of the text :
const getFirstLine = el => {
const text = el.innerHTML;
//set the innerHTML to a character
el.innerHTML = 'a';
//get the offsetheight of the single character
const singleLineHeight = el.offsetHeight;
//split all innerHTML on spaces
const arr = text.split(' ');
//cur is the current value of the text we are testing to see if
//it exceeds the singleLineHeight when set as innerHTML
//prev is the previously tested string that did not exceed the singleLineHeight
//cur and prev start as empty strings
let cur = '';
let prev = '';
//loop through, up to array length
for (let i = 0; i < arr.length; i++) {
//examine the rest of text that is not already in previous string
const restOfText = text.substring(prev.length, text.length);
//the next space that is not at index 0
const nextIndex =
restOfText.indexOf(' ') === 0
? restOfText.substring(1, restOfText.length).indexOf(' ') + 1
: restOfText.indexOf(' ');
//the next part of the rest of the text
cur += restOfText.substring(0, nextIndex);
//set the innerHTML to the current text
el.innerHTML = cur;
//now we can check its offsetHeight
if (el.offsetHeight > singleLineHeight) {
//once offsetHeight of cur exceeds singleLineHeight
//previous is the first line of text
//set innerHTML = prev so
el.innerHTML = prev;
//we can grab the innertext
const firstLine = el.innerText;
const indexOfSecondLine = prev.lastIndexOf('<');
//reset el
el.innerHTML = text;
return firstLine;
}
//offsetheight did not exceed singleLineHeight
//so set previous = cur and loop again
//prev = cur + ' ';
prev += cur.substring(prev.length, cur.length);
}
el.innerHTML = text;
return text;
};
If you want to do the same be careful to wait for the fonts of the web page to be loaded or the width you will be getting will be wrong.
document.fonts.ready.then(function () {})
I have also managed to handle when the title has the text-align: center; property by adding a margin-left/right: auto; to the replica that I then get and add to the calculated left using this :
parseInt(window.getComputedStyle(invisibleTitle).marginLeft)
I know it is not perfect, but since there is no easy way to do that in css it is the only working way that I found.
you can do it if it's possible to separate the two lines in heading,
html:
<header>
<div>
<div class="first-line">
<p>
sample title, pretty long
<div className={css.container}>
<div className={css.shine1}></div>
<div className={css.shine2}></div>
<div className={css.shine3}></div>
</div>
</p>
</div>
<p>
that is wrapping in two lines
</p>
</div>
</header>
css :
header .first-line{
position: relative;
display: inline-block;
}
.container {
position: absolute;
display: inline;
top: 20px;
right: 5px;
}
.container div {
position: absolute;
height: 5px;
border-radius: 5px;
background-color: red;
transform-origin: bottom left;
}
.shine1 {
width: 26px;
transform: rotate(-95deg) translate(20px, 5px);
}
.shine2 {
width: 32px;
transform: rotate(-45deg) translate(20px);
}
.shine3 {
width: 26px;
transform: rotate(0deg) translate(15px);
}
I'm doing a project where I need a scrollable slider with play pause button like www.gap.com. I got this below code from W3C but not sure why multiple images are not showing fully. If I change the width value in CSS code, only first image portion scrolls but it totally ignores the 2nd image. Please anyone help me.
var speed=20 // speed of scroller
var step=3 // smoothness of movement
var StartActionText= "Scroll" // Text for start link
var StopActionText = "Pause" // Text for stop link
var x, scroll, divW, sText=""
function onclickIE(idAttr,handler,call){
if ((document.all)&&(document.getElementById)){idAttr[handler]="Javascript:"+call}
}
function addLink(id,call,txt){
var e=document.createElement('a')
e.setAttribute('href',call)
var linktext=document.createTextNode(txt)
e.appendChild(linktext)
document.getElementById(id).appendChild(e)
}
function getElementStyle() {
var elem = document.getElementById('scroller');
if (elem.currentStyle) {
return elem.currentStyle.overflow;
} else if (window.getComputedStyle) {
var compStyle = window.getComputedStyle(elem, '');
return compStyle.getPropertyValue("overflow");
}
return "";
}
function addControls(){
// test for CSS support first
// test for the overlow property value set in style element or external file
if (getElementStyle()=="hidden") {
var f=document.createElement('div');
f.setAttribute('id','controls');
document.getElementById('scroller').parentNode.appendChild(f);
addLink('controls','Javascript:clickAction(0)',StopActionText);
onclickIE(document.getElementById('controls').childNodes[0],"href",'clickAction(0)');
document.getElementById('controls').style.display='block';
}
}
function stopScroller(){clearTimeout(scroll)}
function setAction(callvalue,txt){
var c=document.getElementById('controls')
c.childNodes[0].setAttribute('href','Javascript:clickAction('+callvalue+')')
onclickIE(document.getElementById('controls').childNodes[0],"href",'clickAction('+callvalue+')')
c.childNodes[0].firstChild.nodeValue=txt
}
function clickAction(no){
switch(no) {
case 0:
stopScroller();
setAction(1,StartActionText);
break;
case 1:
startScroller();
setAction(0,StopActionText);
}
}
function startScroller(){
document.getElementById('tag').style.whiteSpace='nowrap'
var p=document.createElement('p')
p.id='testP'
p.style.fontSize='25%' //fix for mozilla. multiply by 4 before using
x-=step
if (document.getElementById('tag').className) p.className=document.getElementById('tag').className
p.appendChild(document.createTextNode(sText))
document.body.appendChild(p)
pw=p.offsetWidth
document.body.removeChild(p)
if (x<(pw*4)*-1){x=divW}
document.getElementById('tag').style.left=x+'px'
scroll=setTimeout('startScroller()',speed)
}
function initScroller(){
if (document.getElementById && document.createElement && document.body.appendChild) {
addControls();
divW=document.getElementById('scroller').offsetWidth;
x=divW;
document.getElementById('tag').style.position='relative';
document.getElementById('tag').style.left=divW+'px';
var ss=document.getElementById('tag').childNodes;
for (i=0;i<ss.length;i++) {sText+=ss[i].nodeValue+" "};
scroll=setTimeout('startScroller()',speed);
}
}
function addLoadEvent(func) {
if (!document.getElementById | !document.getElementsByTagName) return
var oldonload = window.onload
if (typeof window.onload != 'function') {
window.onload = func;
} else {
window.onload = function() {
oldonload()
func()
}
}
}
addLoadEvent(initScroller)
body {font:1em verdana,sans-serif; color:#000; margin:0}
/* position:relative and overflow:hidden are required */
#scroller { position:relative; overflow:hidden; width:30em; border:1px solid #008080; }
/* add formatting for the scrolling text */
#tag { margin:2px 0; }
/* #testP must also contain all text-sizing properties of #tag */
#testP { visibility:hidden; position:absolute; white-space:nowrap; }
/* used as a page top marker and to limit width */
#top { width:350px; margin:auto; }
<div id="scroller">
<div id="tag">
<img src="https://picsum.photos/1500/600/?image=1"/>
<img src="https://picsum.photos/1500/600/?image=2"/>
</div>
</div>
I got a bit lost in the given JS, and wonder if it is necessary for this relatively simple task.
Here is a method using HTML and CSS for the continuous scrolling and with JS just for the pause/play part.
Because you want continuous scrolling with no gap or jump when the sequence of images goes back to the beginning you need two copies. The tag element is scrolled to the left by half of its width which means that the set of images overwrite themselves so giving a smooth effect.
The JS for the button uses the running value and toggles that.
.playpause {
top: 10%;
left: 10%;
position: absolute;
z-index: 1;
}
#scroller {
width: min(30em, 100vw);
height: min(20em, 100vh);
display: inline-block;
white-space: nowrap;
overflow: hidden;
position: relative;
}
#scroller #tag {
height: 100%;
animation: scroll 10s linear infinite;
animation-fill-mode: forwards;
display: inline-block;
font-size: 0;
}
#keyframes scroll {
0% {
transform: translateX(0);
}
100% {
transform: translateX(-50%);
}
}
img {
height: 100%;
width: auto;
display: inline-block;
}
<div id="scroller">
<button class="playpause" onclick="document.querySelector('#tag').style.animationPlayState = (document.querySelector('#tag').style.animationPlayState != 'paused') ? 'paused' : 'running';">PLAY/PAUSE</button>
<div id="tag">
<img src="https://picsum.photos/1500/600/?image=1" />
<img src="https://picsum.photos/1500/600/?image=2" />
<!-- second copy of all the imaages -->
<img src="https://picsum.photos/1500/600/?image=1" />
<img src="https://picsum.photos/1500/600/?image=2" />
</div>
</div>
Observation: the site linked to in the question (gap) has a slightly unpleasant 'jump' half way through the images so I think they must be using a different method to achieve continuous scrolling.
I have a div that is used to display any info message to the user.
Initial height of this div is zero and display is none. When there's any message to display to the user, i display it using javascript.
I transition the height of this div from 0 to 48px which gives the effect of this div sliding down slowly.
problem
As the height of this is set to 48px, if message inside is long, it doesn't increases it's height to prevent the overflow of text.
Message displayed in full size browser window
Same message displayed in small browser window
question
How can i set the height of this div in javascript so that its height adjusts to fit its contents.
HTML
<!--used for displaying error or success message-->
<p id="info-message-block">
<span></span>
</p>
CSS
.error-msg-block,
.success-msg-block {
display: none;
background: #dc3545;
color: #fff;
border-radius: 2px;
height: 0;
font-weight: 400;
margin: 8px 0 0;
transition: height 0.6s ease-in;
overflow: hidden;
font-size: 85%;
}
.error-msg-block span,
.success-msg-block span {
display: inline-block;
padding: 12px 0 0 20px;
}
javascript function to display this info message
let displayInfoMessage = function (message, messageType) {
'use strict';
let messageBlock = document.querySelector('#info-message-block');
messageBlock.style.display = 'block';
messageBlock.firstElementChild.textContent = message;
if (messageType === 'error') {
messageBlock.className = 'error-msg-block';
} else {
messageBlock.className = 'success-msg-block';
}
setTimeout(function () {
messageBlock.style.height = '48px';
}, 10);
};
As commented above here is the idea using max-height instead of height
let displayInfoMessage = function(message, messageType) {
'use strict';
let messageBlock = document.querySelector('#info-message-block');
messageBlock.style.display = 'block';
messageBlock.firstElementChild.textContent = message;
if (messageType === 'error') {
messageBlock.className = 'error-msg-block';
} else {
messageBlock.className = 'success-msg-block';
}
setTimeout(function() {
messageBlock.style.maxHeight = '120px'; /*Make this value big enough to cover the worst case*/
}, 10);
};
displayInfoMessage("this is a very loooong loooong loooong loooong message for test")
body {
max-width:200px;
}
.error-msg-block,
.success-msg-block {
display: none;
background: #dc3545;
color: #fff;
border-radius: 2px;
max-height: 0;
font-weight: 400;
margin: 8px 0 0;
transition: max-height 0.6s ease-in;
overflow: hidden;
font-size: 85%;
}
.error-msg-block span,
.success-msg-block span {
display: inline-block;
padding: 12px 20px;
}
<p id="info-message-block">
<span></span>
</p>
Simply say messageBlock.style.height = 'auto' instead of setting a fixed value.
If the auto height is wrong, adjust it using padding.
If you want to limit the possible height, set max-height in the stylesheet.
Adding this answer as it seems like possibility of using auto is the real missing piece here, and using max-height is just working this lack of knowledge around.
I have found several codes providing brilliant options to have a text expand upon clicking another one. Expand/collapse functions, I believe we are all somewhat familiar with them.
What I am mainly interested in though, is how could I have expanding text show from a block of text that I already had expanded from another line?
I mean that I'd click a line and it shows another line which I can click again and shows a third one that was not visible before at all.
Is that possible in any way? Thank you very much for any feedback for which you take your time to provide in advance!
I see that you tagged jQuery, so I write small really simple example https://jsfiddle.net/alienpavlov/khk40ygm/2/ using it:
$('.expand').click(function(e) {
e.stopPropagation();
var $expand = $(this).children('.expand.hide');
if ($expand.length >= 1) {
$expand.removeClass('hide');
} else {
$(this).find('.expand').addClass('hide');
}
});
div {
padding: 15px;
}
.hide {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<div class="expand">
Some parent text
<div class="expand hide">
1st level child
<div class="expand hide">
2n level child
</div>
</div>
</div>
In this example you can have more than 2 child elements
You can try something like this: https://jsfiddle.net/julienetienne/41g9f1pt/1/
The key is to get the index, I prefer to put node-list into arrays, that way you can use first class functions.
This rough demo I made will need a polyfill or two for IE8 support:
var section = document.getElementsByTagName('section')[0];
var sectionArray = [].slice.call(section.children);
var lastElement;
section.addEventListener('click', showHide, true);
function showHide(e) {
var target = e.target;
var indexOfSection = sectionArray.indexOf(target);
var elementToToggleHeight;
// If element is a paragraph
if (target.tagName === 'P') {
var elementToToggle = section.children[indexOfSection + 1];
if(indexOfSection + 1 < sectionArray.length){
elementToToggleHeight = parseInt(window.getComputedStyle(elementToToggle, null).getPropertyValue("height"),10);
}
if (elementToToggleHeight) {
// Function to close all except current and first
sectionArray.forEach(function(paragraph){
if(paragraph !== target && paragraph !== sectionArray[0]){
paragraph.style.height = 0;
}
});
} else {
if(elementToToggle)
elementToToggle.style.height = '1em';
}
}
}
p {
font-size: 1em;
cursor: pointer;
height: 0;
overflow: hidden;
background: yellowgreen;
-webkit-transition: all 0.5s ease;
-moz-transition: all 0.5s ease;
-o-transition: all 0.5s ease;
transition: all 0.5s ease;
margin: 0;
padding: 0;
}
p:first-child {
height: auto;
}
<section>
<p>The path of the righteous man is beset on all sides</p>
<p>by the inequities of the selfish and the tyranny of evil men</p>
<p>And I will strike down upon thee with great </p>
<p>vengeance and furious anger those who would attempt</p>
<p>to poison and destroy my brothers</p>
</section>
I have noticed this 'issue' lately when trying some stuff.
Say I want to create a drop-down menu or an accordion.
This is my HTML:
<div class="wrapper" onclick="toggle()">
I want to be animated!
<div class="content">
Was I revealed in a timely fashion?
</div>
</div>
Stylesheets:
.wrapper {
background: red;
color: white;
height: auto;
padding: 12px;
transition: 2s height;
}
.content {
display: none;
}
.content.visible {
display: block;
}
JavaScript:
function toggle () {
var content = document.getElementsByClassName('content')[0];
var test = content.classList.contains('visible');
test ? content.classList.remove('visible') :
content.classList.add('visible');
}
I am trying to achieve a nice, smooth animation when we toggle the state of the content. Obviously this does not work. Anyone can explain to me why it does not work and how to fix it? Many thanks.
Link to the JSFiddle.
First things first, some CSS properties CANNOT be transitioned, display is one of them, additionally only discrete values can be transitioned, so height: auto cannot as well.
In your case the problem is with height: auto, while there are a few hacks for doing this, if you are just showing and hiding stuff, why not add, and use jQuery's toggle instead?
$(".content").toggle("slow");
jsFiddle
--EDIT (without jQuery)--
Because it's the auto that is giving us problems, we can use javascript to replace auto with a value in pixels and then use the css transition normally, if your content doesn't have a scroll, we can easily take that value from the scrollHeight property:
function toggle () {
var content = document.getElementsByClassName('content')[0];
var test = content.classList.contains('visible');
console.log(test);
if (test) {
content.classList.remove('visible')
content.style.height = "0px";
} else {
content.classList.add('visible');
content.style.height = content.scrollHeight + "px";
}
}
Css
.wrapper {
background: red;
color: white;
height: auto;
padding: 12px;
transition: 2s height;
}
.content {
height: 0px;
display: block;
transition: 2s height;
overflow: hidden;
} /* totally removed .content.visible */
jsFiddle