CSS transition of height for injected content into a DIV - html

I'm having a following template:
<div class='content'>
{{content}}
</div>
And following style:
.content {
margin-top: 30px;
background-color: skyblue;
width: 300px;
transition: all linear 0.5s;
}
Please note that {{content}} will grow or shrink (technically to extend a card to show more information or hide it). I've already set the css transition and it does work when manually setting a different height to the element. However when more content is injected into content no transition is made, just a plain old resizing. Any help of getting the transision right?
See following plunkr
Thanks,
Amit.

I believe that's quite normal, transitions apply only to changes to the CSS, not for computed changes.
One option might be to have a nested div, set overflow: hidden on the outer one, then get the computed height of the inner one and set it on the outer one to get the transition.
CSS:
#outer {
margin-top: 30px;
background-color: skyblue;
width: 300px;
transition: all linear 0.5s;
overflow: hidden;
}
HTML:
<button id="more" onclick="increase();">More</button>
<button id="more" onclick="decrease();">Less</button>
<div id="outer">
<div id="inner">
lorem ipsum
</div>
</div>
JS:
function increase()
{
var o = document.getElementById('inner');
var t = o.innerHTML;
for (var i=0 ; i<20;i++)
t += " lorem ipsum";
o.innerHTML = t;
adjustHeight();
}
function decrease()
{
var o = document.getElementById('inner');
o.innerHTML = "lorem ipsum";
adjustHeight();
}
function adjustHeight()
{
var i = document.getElementById('inner');
var o = document.getElementById('outer');
o.style.height = window.getComputedStyle(i).height;
}
Working fiddle: http://jsfiddle.net/Lnts7w1f/

With the help of #jcaron answer I managed to sort this in a react component. So when the 'activeSubsection' button is clicked the useEffect hook is run.
const [topicsHeight, setTopicsHeight] = React.useState("0px")
const topicsDiv = React.useRef();
React.useEffect(() => {
setTopicsHeight(topicsDiv?.current?.offsetHeight + "px")
}, [activeSubsection])
{!hideTopics &&
<div style={{ overflow: "hidden", height: topicsHeight, transition: "0.2s" }}>
<Topics ref={topicsDiv} className="largeScreen">
{!!subsectionTopics && subsectionTopics.sort((a, b) => a.node.slug.localeCompare(b.node.slug)).map((topic, index) =>
<Link key={index} href={'/'+slug+'/'+topic.node.slug} scroll={false}>
<Topic
active={activeTopic == topic.node.slug} transition={true} bold={activeTopic == topic.node.slug}
>
{topic.node.title}
</Topic>
</Link>
)}
</Topics>
</div>
}
Ovbiously that's not the full component but hopefully enough to give you an idea.

Well... There's a trick you can do for that... I don't know if it will fit your needs.
The css transition effect is applied on css properties that have a previous value, and then change. Though you are indeed changing the content's height of the div, the actual css property height is not explicitly changing. That's why you don't get the animation.
A workaround is to find the inner height of the div and then set it to the element, causing it to animate. I've created a function for that, and added a span to control the inner size of the div.
Just call the function on every change:
<div class='content'>
<span id="height-control">
{{ctrl.content}}
</span>
</div>
JS function:
var spn = document.getElementById("height-control");
var UpdateHeight = function () {
var h = spn.offsetHeight;
spn.parentNode.style.height = h + "px";
};
http://plnkr.co/edit/p6QRAR5j4C8d0d0XRJRp?p=preview

Related

fit text inside an element with fixed width

I have a list of buttons that uses roman numerals as label, something like this:
{list.map((item, index) => {
return (
<li key={`${item.id}`}>
<button
onClick={() => handleChange(index)}
>
{transformNumberToRomanNumeral(index + 1)}
</button>
</li>
)
})}
because of that, the numerals will have different sizes, like this: "I", "II", "III".
I need to make all the buttons the same size, both width and height, button if I set the width to a fixed value, some text will still overflow the button width. Is there a way to make the text scale to fit the max width, but never overflow it?
I tried using using svg with text tag, but the scale looks really bad, it usually don't get all the height so the text end ups really small.
In the example below, the buttons have a consistent width of 50px.
Where n is the number of characters in the roman numeral, both the:
font-size
line-height
are: (14 - n)
except where (14 - n) < 6, in which case that value remains 6.
Working Example:
let buttons = document.querySelectorAll('button');
for (let button of buttons) {
const numeralCount = button.textContent.length;
let textSize = (14 - numeralCount);
textSize = (textSize < 6) ? 6 : textSize;
button.style.setProperty('--text-size', textSize + 'px');
}
:root {
--text-size: 6px;
}
button {
display: block;
width: 50px;
height: 18px;
margin: 6px 0 0;
padding: 0 2px;
line-height: var(--text-size);
font-size: var(--text-size);
overflow-x: hidden;
}
<ol>
<li><button>M</button></li>
<li><button>MD</button></li>
<li><button>MDC</button></li>
<li><button>MDCC</button></li>
<li><button>MDCCC</button></li>
<li><button>MDCCCL</button></li>
<li><button>MDCCCLX</button></li>
<li><button>MDCCCLXX</button></li>
<li><button>MDCCCLXXX</button></li>
<li><button>MDCCCLXXXV</button></li>
<li><button>MDCCCLXXXVI</button></li>
<li><button>MDCCCLXXXVII</button></li>
<li><button>MDCCCLXXXVIII</button></li>
</ol>
I managed to do something that seems to look like what you want. Here is the Stackblitz and here is the code :
import React from 'react';
import { render } from 'react-dom';
import './style.css';
const App = () => {
return (
<div className="container">
<button>
<SvgRenderer text="VII" />
</button>
<button>
<SvgRenderer text="LXXIII" />
</button>
<button>
<SvgRenderer text="LXIII" />
</button>
<button>
<SvgRenderer text="LXXIII" />
</button>
<button>
<SvgRenderer text="LXXXIII" />
</button>
</div>
);
};
const SvgRenderer = ({ text }) => (
<svg
width="100%"
height="100%"
viewBox="0 0 100 50"
preserveAspectRatio="xMinYMid meet"
// style={{ backgroundColor: 'green' }}
>
<text
x="0"
y="45"
fill="black"
lengthAdjust="spacingAndGlyphs"
textLength="100"
>
{text}
</text>
</svg>
);
render(<App />, document.getElementById('root'));
I based this on this post. Since I couldn't manage to make the checked answer to work properly, I used one of the answer bellow, but note that I'm absolutely not a pro svg so there might be a way better answer than mine using SVG (or other answers).
[previous answer] You coud do this by using flexbox. This way you don't need to set with and height. Here is the Stackblitz and here is the code :
import React from 'react';
import { render } from 'react-dom';
import './style.css';
const App = () => {
return (
<div className="container">
<button>some text</button>
<button>Some more text</button>
<button>Some more text longer</button>
<button>Some more text longer again and again</button>
<button>Some more text longer again and again, and again ?</button>
</div>
);
};
render(<App />, document.getElementById('root'));
And some css :
.container {
display: flex;
flex-wrap: wrap
}
.container > button {
flex: 1
}
I used long string in the buttons for the use case, but with roman numerals it should be ok I guess.
You can iterate through the buttons, using a dummy button to notice when the text just fits within the button width.
This snippet starts with a largeish font size assuming the button width is 50px and gradually brings the font size down until the text fits.
<style>
.container {
widht: 100vw;
display: flex;
}
button {
width: 50px;
height: 50px;
padding: 0px;
text-align: center;
clear: both;
}
</style>
<body>
<div class="container">
<button>I</button>
<button>II</button>
<button>III</button>
<button>VIII</button>
<button>MMCXXII</button>
</div>
</body>
<script>
const buttonW = 50;
const buttons = document.querySelectorAll('button');
const dummy = document.createElement('button');
let w, fs ;
document.body.appendChild(dummy);
dummy.style.padding = 0;
buttons.forEach( button => {
fs = 40;
dummy.style.width = 'auto';
dummy.style.fontSize = fs + 'px';
dummy.innerHTML = button.innerHTML;
w = window.getComputedStyle(dummy).width.replace('px', '');
while (w > buttonW) {
w = window.getComputedStyle(dummy).width.replace('px', '');
fs--;
dummy.style.fontSize = fs + 'px';
}
button.style.fontSize = fs + 'px';
});
dummy.remove();
</script>

CSS set height related to the width

I'm making a album page with HTML and CSS and I want to set my height of the image to be 9/16 of the width.
HTML code
<div class = "col-md-12" id = "album-list">
<div class = "col-md-4">
<div id = "album-card" style = "border: 1px solid grey;">
<img class = "image_thumbnail" src = "images/sampleImage.jpg" alt = "Thumbnail" />
<div style = "padding-top: 10px; padding-bottom: 5px;">
<p class = "card-text"><strong>TITLE HERE</strong></p>
</div>
</div>
</div>
<!-- More Columns -->
</div>
CSS style
.image_thumbnail {
width: 100%;
}
I saw some posts on stackoverflow with similar questions, and the answer was to use padding-bottom to set the height according to the width. However, since I gave border around #album-card, #album-card gets longer in height than it's supposed to be.
How can I set the heigth 9/16 of the width??
To convert the images:
var images = document.querySelectorAll('.image_thumbnail');
for (var i = 0; i < images.length; i++) {
images[i].height = images[i].width * 0.5625;
}
This will select all the images on the page with the according class and will directly convert the height relative to their width (as: 16 / 9 = 0.5625).
With the help calc() in css3 you can calculate width.
.image_thumbnail {
width: calc((100%*9)/16);
}
This is Referance calc(), This may be helpful for you.

Bootsrap Affix not working

I have added the library for Bootstrap and data-spy attribute where I want to make the div fix when I scroll the page down. But it doesn't work, I have almost tried everything, but not able to figure out the problem.
Is is something like the data-spy attribute doesn't work on class = "row" ?
Here's my code for HTML.
<div class="row">
<h4> HEADING </h4>
<h5>
<div class="row" data-spy="affix" data-offset-top="10">
dsds
Date : <input type="date" name="graph_date" id="graph_date">
</div>
<div class="row">
<div class="graph-hourly">
<div class="loader" id="chart_loader">
<p>Loading...</p>
</div>
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart_hourly"></div>
</div>
</div>
</div>
and some css :
.affix {
top : 0;
width: 80%;
}
after searching for some solutions, I've added this also,
.affix-top {
width: 100%;
}
.affix-bottom {
position: absolute;
width: 100%;
}
but this solution also dosen't worked for me.
Not sure what the problem is in your case. I copied and pasted your code into a jsfiddle with the bootstrap library and the affix class did work. Though, it worked badly because it affixed the row right when you started scrolling.
Looks like Bootstrap doesn't have a way to set the offset to the current position of the element so I added the following javascript to make it work.
$('#affix-this').affix({
offset: {
top: $('#affix-this').offset().top
}
})
(#affix-this should be changed to the id of the row you want to affix.)
Note the $('#affix-this').offset().top. This makes sure the element gets affixed right when you reach the element's current position.
Second, I removed the html attributes that you had for the affixing.
<div class="row">
<h4> HEADING </h4>
<div class="row" id="affix-this">
dsds Date :
<input type="date" name="graph_date" id="graph_date">
</div>
<div class="row">
<div class="graph-hourly">
<div class="loader" id="chart_loader">
<p>Loading...</p>
</div>
<div id="chart_hourly"></div>
</div>
</div>
Notice the affix-this id was added to the row that you want to affix.
Here is a working JSFiddle with these changes so you can see it in action: https://jsfiddle.net/heraldo/6s4u26m3/4/
First of all delete top:0; from affix class because it will make a issue for you.
now you have two methods pick a one :
1
adding data-spy="affix" which is works fine for me
2
same result ass data-spy but you will need some styling after you complete your page
by adding a position Property for input tag
as ex. :
CSS:
sticky{
position:fixed;
}
and HTML :
<input type="date" name="graph_date" class="sticky" id="graph_date">
Update 1
this Jquery code can detect a scroll event so when user scroll down it will make the div tag sticky or "affix"
$(function() {
$(window).scroll(function() {
var aTop = "100";
if ($(this).scrollTop() >= aTop) {
$('#affix-this').css( "position", "fixed" );
$('#affix-this').css( "top", "0" );
$('#affix-this').css( "width", "100%" );
}
});
});
change the aTop variable with the height you want (in pixel) so when the user scroll down 100px the div become sticky
a JSfiddle example
Update 1.1
a bit smarter Jquery code do the same but get the height automatically from a another element this can be good if you format your page to something similar to this
$(function() {
$(window).scroll(function() {
var aTop = $('id').height();
if ($(this).scrollTop() >= aTop) {
$('#affix-this').css( "position", "fixed" );
$('#affix-this').css( "top", "0" );
$('#affix-this').css( "width", "100%" );
}
});
});
Make sure the element to which you're adding data-spy="affix has been created in the DOM before your Bootstrap scripts load. I ran into an issue where I was adding data-spy="affix" in my HTML, but it was wrapped up in a section that wasn't rendering, thanks to data-ng-if. My HTML was created after my Bootstrap had loaded, so the <div> I wanted to stick to the top of the screen never stayed in a fixed position. If you can, use data-ng-show, or something that merely hides HTML, rather than prevents it from being created on page load.

Making a calendar component scrollable on x and y axis

I'm creating a calendar component with my own HTML/CSS.
It can have multiple categories across it's header horizontally (x-axis) and it will have up to 24 time slots down it's side vertically (y-axis).
The category headers must always be visible when scrolling vertically and the time slots must always be visible when scrolling horizontially.
How can I achieve this via css?
See screenshot for what I want (Image more tracks across header that results in content overflow).
Maybe take this as my sinple html structure:
<div class="calendar">
<div class="calendarColumnHeaders"></div>
<div class="calendarGrid">
<div class="timeSlotsColumn"></div>
<div class="tracksContainer"></div>
</div>
</div>
Thanks
Give <div class="tracksContainer"></div> an extra class called stick
add this Css:
.stick {
position:fixed;
top:0px;
}
And unfortunately, you do need some Jquery
jQuery – Calculates the position of the sticker div and makes its position fixed if the page has scrolled that far.
$(document).ready(function() {
var s = $(".tracksContainer");
var pos = s.position();
$(window).scroll(function() {
var windowpos = $(window).scrollTop();
s.html("Distance from top:" + pos.top + "<br />Scroll position: " + windowpos);
if (windowpos >= pos.top) {
s.addClass("stick");
} else {
s.removeClass("stick");
}
});
});
overflow-x: scroll; //horizontal
overflow-y: scroll; //vertical

Expanding Vertical Space Issue with Floated Elements

I have a layout that requires a list of items to be organized into two vertical columns. The items are in a single list of <div>s, with the markup also being re-purposed for mobile, so I don't want to modify the structure.
The problem I have here is that each item has an expanding content area which the user can toggle, and when this content is expanded the vertical space for that column needs to expand downward with the other column staying fixed.
Right now I have a basic solution with floated items, but when I expand the content areas the vertical space expands in both columns instead of just the one.
Here's a link to an example of the functionality as I have it now, and below is a screenshot of what the desired behavior should be.
Is it possible to style this to support the required behavior? Or am I going to have to modify the structure of items in order to get this to work? Thanks in advance for your help!
Your premise is flawed. Document structure flows left-to-right, top-to-bottom. You will need to make some change to the structure... Easiest would be adding two containers for a left column and a right column. Otherwise, you're in for some tricky absolute positioning markup, and a little funky jquery, which I can only suggest with the addition of some unique IDs for each of the panels.
I would, personally, add ids such as panel1 through panel4 per your example, then use this javascript (or similar) as a jumping off point:
for(var i=1; i<=4; i++) {
$('#panel'+i).css('left', function(index) {
if(i%2 == 0) return "120px";
else return "0px";
});
}
$('.more').click(function(e) {
e.preventDefault();
$(this).parent().children('p').toggle();
var id = $(this).parent().attr("id");
switch( id ) {
case 'panel1':
console.log("panel1 found");
$('#panel3').css('top', function(index) {
var buffer = $('#'+id).height() + 20 + "px";
return buffer;
});
break;
case 'panel2':
$('#panel4').css('top', function(index) {
var buffer = $('#'+id).height() + 20 + "px";
return buffer;
});
break;
default: break;
}
});
With the default values in the css for those panels:
#panel1 { top:0px; }
#panel2 { top:0px; }
#panel3 { top:56px; }
#panel4 { top:56px; }
The less you tweak the html, the more work you'll create in javascript.
edit:
Proposed alternate Javascript to remove need to alter HTML, assuming two elements per row. Since we know the row to be the problem...
var ct = 1
$('#container > div').each(function(index, domEle) {
$(domEle).attr('id', 'panel'+ct);
$('#panel'+ct).css({
'position': 'absolute',
'left' : function(index, value) {
if(ct%2 == 0) return "120px";
else return "0px";
},
'top' : function(index, value) {
return (56 * Math.floor(ct/3)) + "px";
}
});
ct++;
});
$('.more').click(function(e) {
e.preventDefault();
$(this).parent().children('p').toggle();
var id = $(this).parent().attr("id");
switch( id ) {
case 'panel1':
$('#panel3').css('top', function(index) {
var buffer = $('#'+id).height() + 20 + "px";
return buffer;
});
break;
case 'panel2':
$('#panel4').css('top', function(index) {
var buffer = $('#'+id).height() + 20 + "px";
return buffer;
});
break;
default: break;
}
});
Now no changes need be made to the HTML, though you'll want to redo the click function to handle repositioning of elements after a click. I would make life easy and hide all .more items before expanding a new box, since it would mean having to calculate the heights of all elements above, but how much work you want to make is your business.
Here's a PURE CSS SOLUTION for four panels (I don't know if you intended to have more, and I do not have six [2 wide 3 high] working yet--and suspect it is not possible). It works in FF and IE8/9. See the fiddle: http://jsfiddle.net/bEgwB/203/
However, IE8 experiences a redraw bug that keeps panel 3 from moving so it needs extra javascript help (added in fiddle above), and IE7 needs some margin adjustments to get panel 4 positioned correctly (but does not have the redraw issue even without the extra javascript help). UPDATE (11-18-11): here's the fiddle for IE7 margin adjustment: http://jsfiddle.net/bEgwB/286/
EDIT: a previous version of my CSS had display calls that were unnecessary.
HTML
<div id="container">
<div class="panel one">
Panel 1<br />
<a class="more" href="#">more</a>
<p>More Info 1 with some additional content</p>
</div>
<div class="panel two">
Panel 2<br />
<a class="more" href="#">more</a>
<p>More Info 2 with some additional content</p>
</div>
<div class="panel three">
Panel 3<br />
<a class="more" href="#">more</a>
<p>More Info 3 with some additional content</p>
</div>
<div class="panel four">
Panel 4<br />
<a class="more" href="#">more</a>
<p>More Info 4 with some additional content</p>
</div>
</div>
CSS
a {color:yellow;}
#container {width:0 ; padding: 0 130px;}
.panel {
background-color:green;
padding:5px;
color:#fff;
width:100px;
}
.panel p {display:none;}
.panel.one {
float: left;
margin:10px 0 10px -120px;
}
.panel.two {
float: right;
margin: 10px -120px 20px 0;
}
.panel.three {
float: left;
clear: left;
margin:10px 10px 10px -120px;
}
.panel.four {
clear: right;
margin: 10px;
}
JAVASCRIPT
$('.more').click(function(e) {
e.preventDefault();
$(this).parent().children('p').toggle();
/* the following fix IE8 */
$(this).parent().parent().hide();
$(this).parent().parent().show();
});
You need to add two more div's - left and right column and split your items between these two divs. This is the way how to make them independent, here is jsfiddle for this.
HTML
<div id="container">
<div class="left">
<div class="panel">
Panel 1<br />
<a class="more" href="#">more</a>
<p>More Info 1 with some additional content</p>
</div>
<div class="panel alt">
Panel 2<br />
<a class="more" href="#">more</a>
<p>More Info 2 with some additional content</p>
</div>
</div>
<div class="right">
<div class="panel">
Panel 3<br />
<a class="more" href="#">more</a>
<p>More Info 3 with some additional content</p>
</div>
<div class="panel alt">
Panel 4<br />
<a class="more" href="#">more</a>
<p>More Info 4 with some additional content</p>
</div>
</div>
</div>
CSS
a {color:yellow;}
#container {width:300px; position:relative;}
.panel {background-color:green;padding:5px;color:#fff;width:100px;margin:10px;}
.panel p {display:none;}
.left {
width: 50%;
float: left;
}
.right {
width: 50%;
float: right;
}
May be you can do it column-count property like this:
a {color:yellow;}
#container {
width:300px;
-moz-column-count: 2;
-moz-column-gap: 50%;
-webkit-column-count: 2;
-webkit-column-gap: 50%;
column-count: 2;
column-gap: 50%;
}
.panel p {display:none;}
.panel {background-color:green;padding:5px;color:#fff;width:100px;margin:10px;}
.alt{
margin-bottom:90px;
}
Check this fiddle http://jsfiddle.net/bEgwB/87/
UPDATED
Check this:
http://jsfiddle.net/bEgwB/276/
There is a styling solution. To tie the two columns together they need an extra level of binding you can do that by adding a style attribute to "container", "display:table" and to the panels with "display:table-cell".
That will keep the heights in synch. Both "container" and the "panel" class must have a declared width or they really mess up the layout. IE is weak on support of these attributes, so that could be a problem with the solution.
If I correctly inderstand, that do you you want is something like this
http://jsfiddle.net/bEgwB/275/
You need to use both css properties display:inline-block and float:left to implement effect like on your jpg. If my markup right and this looks as expected, I may help you with javascript, if it not ok now
Good luck.