Freeze Header until Irrelevant (HTML, CSS and JS) - javascript

I have a few different tables that I want to display vertically down a page. The trouble is, the different tables have different column headings.
In the picture, the dark border represents the viewport. The dotted red border is where the red header belongs but note that it is "frozen" to the top of the viewport. This is true until, in the second image, the green header begins to replace it.
http://imagebin.org/172108
Does anyone know how this could be accomplished using very lightweight scripting (In other words, I don't want JQuery or something) and CSS. I have the advantage that I only need it to render on Webkit (which means some css3 is also an option). I don't care if the headers are not actually part of the html <table> - they must obviously just line up properly though.

edit: http://jsfiddle.net/BCtP8/3/
There, I fixed the hiccuping text. When the header becomes fixed, I change the placholder's height. I also changed it to work with any header height, not just the one I had given them.
OLD POST
Here you go man:
http://jsfiddle.net/BCtP8/
Sorry for any problems that might occur, I'm quite new to the whole web developing scene and I don't have the experience to predict what might happen.
I doubt this is the best or most efficient way to do it, so if you find better, please post it here so I can learn. Thanks.

This will only be possible with Javascript - I can't think of a way of doing it simply with CSS.
The basis of the answer is to use position:fixed on the element. I would suggest cloning the thead of your table to attach in a fixed position at the top of the table, and then add an event listener for the scroll event and check the position of each table against the amount that has been scrolled.
I've put up an example on JSFiddle.

For what it's worth:
There are a couple of answers here but I don't think they actually answer the question. They don't operate on table headers (thead elements) or they use 3rd party libraries. There are some subtle issues with lifting a thead out of the table - the largest of which is that the cells will collapse down if the header text is wider or narrower than the data in the tbody.
Here is my solution that solves the problem without any libraries and working on table headers. It makes no assumptions about the styling of the table, or the size of the headers; everything is calculated. Only tested in Chrome per the requirements of the OP.
Script:
function initFloatingHeaders() {
var tables = document.querySelectorAll('table.float-header');
var i = tables.length;
while (i--) {
var table = tables[i];
var wrapper = document.createElement('div');
wrapper.className = 'floating-header';
var clone = table.cloneNode(true);
wrapper.appendChild(clone);
table.parentNode.insertBefore(wrapper, table);
var thead = table.querySelector('thead');
wrapper.style.width = thead.scrollWidth + 'px';
wrapper.style.height = thead.scrollHeight + 'px';
wrapper.style.left = table.offsetLeft + 'px';
}
window.addEventListener('scroll', function() {
var headers = document.querySelectorAll('div.floating-header');
var bodyHeight = document.body.offsetHeight;
var i = headers.length;
while (i--) {
var header = headers[i];
var tableBounds = header.nextSibling.getBoundingClientRect();
if (tableBounds.top < 0 && tableBounds.bottom > 0) {
header.style.display = 'block';
} else {
header.style.display = null;
}
}
}, false);
}
Tables should have the class float-header applied and initFloatingHeaders should be called on load or documentReady. Example: http://jsbin.com/ulusit/2 (Old example with bad transitions: http://jsbin.com/ulusit/)

It's possible to achieve the result in pure CSS with position:sticky 🤞
body, dl, dt, dd {
margin: 0;
}
dl {
position: relative;
}
dt, dd {
padding: 1rem;
}
dt {
background-color: #ddd;
position: sticky;
top: 0;
box-shadow: 0 0 0.25rem rgb(0 0 0 /10%);
}
I got a pen 4 years ago. You can jump in to see how it works:
https://codepen.io/patrickliu/full/MGPNgJ

Related

Parallax - Element Glitching

So I've been trying to wrap my head around this neat effect called Parallax. Where basically the background scrolls slower than the foreground elements.
I've found this new "trick" which is working. Changing the top property to create the parallax effect, as the scrolling goes.
The issue...
So, for performance purposes and lifting the stress from the CPU when the element is not inside the user's viewport, I've created an if statement, which checks if the top position is more than 300px. If it is, it overwrites everything and sets the top property back to 0, so it won't keep increasing it for no reason.
Now, just scroll for a bit. See how, as the red div comes over the white one, the white one stutters? Looking in the DOM inspector, I see that the if statement is freaking out, setting the top property to 0px even if it's not more than 300px. Halp.
While we're at it, I'd love to see more suggestions regarding parallax effects. I've seen a few answers already regarding this effect, but they seem... overly complicated for me. I know there are better ways to do this, I know there are.
And also, It would be greatly appreciated if there were no jQuery answers. Thanks.
var txtfirst = document.getElementById("txtfirst");
window.onscroll = function(){
var ypos = window.pageYOffset;
txtfirst.style.top = ypos * 0.4 + "px";
if(txtfirst.style.top > '300px'){
txtfirst.style.top = '0px';
}
}
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
.text-first {
display: flex;
text-align: center;
justify-content: center;
align-items: center;
font-size: 32px;
font-family: Arial;
color: gray;
width: 100%;
height: 500px;
position: relative;
}
.foreground-red {
width: 100%;
height: 600px;
background-color: red;
display: flex;
justify-content: center;
align-items: center;
font-family: Arial;
color: gray;
font-size: 32px;
}
.spacer { /*for scrolling purposes*/
width: 100%;
height: 1000px;
}
<div class="text-first" id="txtfirst">THIS IS SOME TEXT</div>
<div class="foreground-red">THIS SHOULD GO ABOVE</div>
<div class="spacer"></div>
Your application most likely (you only provided selected snippets, I assume) does not work at least because you are comparing text when you want to be comparing numbers, in your attempt at optimization:
txtfirst.style.top > '300px'
The above will not behave like what you'd expect it to. Every property of the Element::style property (e.g. txtfirst.style in your case) is a text string, not a number. A test like "50px" < "300px" does not compare whether 50 is less than 300, it compares the text values lexicographically.
If you actually want to compare the amount of pixels, you can use parseInt function to convert a value like 50px to a number, 50. Your test will then look as follows:
parseInt(txtfirst.style.top) < 300
Now, what follows is a number of problems with your approach to solving this and suggested solutions, since you are interested in suggestions.
Using inline styles is problematic in general (subjective)
Inline styles have the highest precedence in CSS, which can be problematic in cases where the user has their own style sheets, as properties set in those will be ignored in favor of properties set inline.
Reading properties of inline style back assuming that would be the actual used value, is just plain wrong. Inline style object tracks assigned values, not computed or used values. The Window::getComputedStyle(element) function, on the other hand, retrieves computed style for the element.
Solution? Reading properties using getComputedStyle and writing them directly to a preferred (or empty, if so desired) stylesheet (document.styleSheets, reflecting all link rel=stylesheet and style elements):
function rule(selector) {
var sheet = document.styleSheets[0];
return Array.prototype.find.call(sheet.cssRules, rule => rule.selectorText == selector);
}
var txtfirst = document.getElementById("txtfirst");
window.onscroll = function() {
var ypos = window.pageYOffset;
var style = rule(".text-first").style;
style.top = ypos * 0.4 + "px";
if(parseInt(getComputedStyle(txtfirst).top) > 300) {
style.top = "0px";
}
}
The rule function above returns the CSS rule (one containing the set CSS properties) with matching selector (e.g. .text-first or html, body) from the first found stylesheet (you only have one). The style property of a a rule refers to an object which contains all CSS properties set in the rule. It behaves the same as the inline style object. Observe that you aren't using inline styles anywhere above, you write to the stylesheet object (as initialized by the <style>...</style> fragment of your document) and read back computed values.
Fixing problems with using scroll event for animation
First of all, did you know that older versions of iOS did not fire the scroll event as you scrolled? That would stop your parallax effect dead in its tracks, as a single scroll event would be fired after the user stops scrolling. This has to do with the way browsers do page scrolling -- to achieve smooth page scrolling animation using constrained mobile CPU resource, running JavaScript code courtesy of a scroll event handler 60 times per second was just deemed too generous an offer, and Apple instead went for the controversial solution, occupied with good UX as they are.
Anyway, what to do then if not use scroll event? You could use the good old setInterval:
function rule(selector) {
var sheet = document.styleSheets[0];
return Array.prototype.find.call(sheet.cssRules, rule => rule.selectorText == selector);
}
var txtfirst = document.getElementById("txtfirst");
var old_window_pageYOffset = window.pageYOffset;
setTimeout(function() {
var ypos = window.pageYOffset;
if(ypos != old_window_pageYOffset) return;
old_window_pageYOffset = ypos;
var style = rule(".text-first").style;
style.top = ypos * 0.4 + "px";
if(parseInt(getComputedStyle(txtfirst).top) > 300) {
style.top = "0px";
}
}, 1000 / 60);
What the above does is makes sure a function is called 60 times per second for the entire lifetime of your page, but checks on every invocation if the scroll position of the window has changed since last invocation, invoking the old code only if it has, and doing nothing otherwise. This obviously does not use the scroll event at all. All this said, newer iOS releases have since reverted the behavior and the scroll event is fired with every change of the scrolling position. Meaning you may simply want to use that as baseline and depend on the event instead of setInterval. A free benefit of the latter is that you control the rate at which your parallax effect runs.
I can also suggest using requestAnimationFrame, which is more "intelligent" than setInterval in that the user agent will not invoke your code if it deems animation unnecessary, for example if the entire page tab isn't visible or interactive with the user at the moment. Rest assured your animation will run "when needed":
function rule(selector) {
var sheet = document.styleSheets[0];
return Array.prototype.find.call(sheet.cssRules, rule => rule.selectorText == selector);
}
var txtfirst = document.getElementById("txtfirst");
var old_window_pageYOffset = window.pageYOffset;
requestAnimationFrame(function() {
var ypos = window.pageYOffset;
if(ypos != old_window_pageYOffset) return;
old_window_pageYOffset = ypos;
var style = rule(".text-first").style;
style.top = ypos * 0.4 + "px";
if(parseInt(getComputedStyle(txtfirst).top) > 300) {
style.top = "0px";
}
});
The code above is an okay attempt at the parallax effect, save for minor nitpicks which have little to do with parallax effect alone:
I don't use the on*name* family of functions when we have addEventListener. The former is one property for each handler, and there are no guarantees your script is the sole consumer of these properties -- they may already be set by a browser extension. We can argue whether the Web page author has exclusive ownership and access to all properties they can get their hands on, but at least I have explained my rationale. There is no significant drawback known to me for using addEventListener("scroll", function() { ... }) instead.
You don't need to use both a class name and an ID for an element to refer to it. document.querySelector(".text-field") will return the first available element that has "text-field" among its list of class names.
I've saved the best for last -- Pure CSS Parallax Websites goes through achieving (although not without some small hacks for browsers with bugs) desired effect without any JavaScript at all, relying on the CSS perspective property and some others. It also mentions some of the same things I've warned about above, things that I have attempted to circumvent and explain.
If you don't want to read (and understand) documentation, I suggest you resort to using a convenient abstraction -- a plugin, a framework, a library or something to that end that will save you from having to grok the intricacies of this. Modern CSS and compliant browser model are complex enough for these solutions to exist and thrive.

Lock table headers when scrolling (ColResizable)

I am not very familiar with HTML, CSS and Javascript, and yet I am tasked with creating a table system that will allow the user to resize table columns but also have said headers fixed to the top of the section the table is displayed in when the user scrolls the table.
I know this sounds confusing, so i created a Fiddle that can accurately represent what I have currently and hopefully where it needs to be updated.
I have settled on using the plugin JSColResizable found here:
http://www.bacubacu.com/colresizable/
and have set it up in the fiddle allowing the table to be resized. I have also wrapped the table in a div that only allows 300px height to be displayed at any time.
When the user mouses over the table division and scrolls down, the table headers scroll outside of the division making it difficult for users to relate which column was what. I simply need the table entries to continue to work the same way and scroll outside the div, yet allow the headers to remain static at the top of the div so that they can more easily be related to the columns.
If anyone has experience with this issue, I would greatly appreciate any help that can be offered.
You have to write a little jquery for it, and a class in css
Here is the Working Fiddle of what you want.
I have written some jQuery code for it you can use it.
jQuery
$(function(){
$.fn.fixMe = function () {
return this.each(function () {
var $this = $(this),
$t_fixed;
function init() {
$this.wrap('<div class="container" />');
$t_fixed = $this.clone();
$t_fixed.find("tbody").remove().end().addClass("fixed").insertBefore($this);
resizeFixed();
}
function resizeFixed() {
$t_fixed.find("th").each(function (index) {
$(this).css("width", $this.find("th").eq(index).innerWidth() + "px");
});
}
function scrollFixed() {
var offset = $(this).scrollTop(),
tableOffsetTop = $this.offset().top,
tableOffsetBottom = tableOffsetTop + $this.height() - $this.find("thead").height();
if (offset < tableOffsetTop || offset > tableOffsetBottom) {
$t_fixed.hide();
}
else if (offset >= tableOffsetTop && offset <= tableOffsetBottom && $t_fixed.is(":hidden")) {
$t_fixed.show();
}
var tboffBottom = (parseInt(tableOffsetBottom) );
var tboffTop = (parseInt(tableOffsetTop));
if (offset >= tboffBottom && offset <= tableOffsetBottom) {
$t_fixed.find("th").each(function (index) {
$(this).css("width", $this.find("th").eq(index).outerWidth() + "px");
});
}
}
$(window).resize(resizeFixed);
$(window).scroll(scrollFixed);
init();
});
};
$("table").fixMe();
});
Hope this helps you.
There is currently no native way for a table to have sticky headers and footers. All libraries that provide that functionality use divs or other tags to achieve that. That being said, I would probably not try to reinvent the wheel here. You could give SlickGrid a try - it is pretty stable and works great even with huge data-sets
Here is a pure CSS way of making the table scrollable without the use of a plugin or library, it uses table-layout:fixed to make the header fixed. Check out this example, meybe it is what you want.
.fixed_headers {
width: #table_width;
table-layout: fixed;
border-collapse: collapse;
Check out the full code of the tabe in this codepen link
https://codepen.io/tjvantoll/pen/JEKIu

Floating table headers when container has overflow?

I'm using position:fixed to float some headers in my table when the user scrolls past the top, ala this method: http://css-tricks.com/persistent-headers/
Everything works great on regular pages, but whenever I have a table inside of another div or something with a fixed height and overflow:auto it blows up spectacularly.
What do I need to do to account for not just the page-wide scrolling, but also the scrolling of my container? And to account for scrolling past the 'top' of said container?
Thanks for any direction you guys can point me in.
Here's my existing code:
var mainheader = table.rows[0];
var tableHeight = table.getHeight();
var tableScroll = table.viewportOffset();
var headerHeight = mainheader.getHeight();
// TODO: If we're scrolling a subcontainer, we need to get the offset for that too! Somehow?
// If tableHeight < 1, it means our table his hidden right now, so skip it
if (tableHeight < 1)
continue;
// If we've scroll down past the very tip top of the table, but haven't yet scroll past the end of it, show our floating headers
if (tableScroll.top < 0 && tableHeight + tableScroll.top - headerHeight > 0)
{
table.floatingheader.style.display = '';
// Handle horizontal scrolling too!
table.floatingheader.style.left = (tableScroll.left + 1) + 'px'; // 1px offset for border
}
else
table.floatingheader.style.display = 'none';
NOTE: I have access to prototype.js, but do not have jQuery or any other 3rd party library. :/
I realize you're not using jQuery but you may want to look at this guys code on github and see how he implements it, then modify it for your purposes: http://webpop.github.com/jquery.pin/

How can I alter the FixedTableHeader jQuery plugin to have a fixed first column as well?

I'm using a jQuery plugin from here http://www.tablefixedheader.com/ to make a snazzy table with a fixed heading, sorting and other cool features. Now, I've also looked at jqGrid, which looks ridiculously awesome, but we are doing some funky things with our data source and I don't think it is quite ready to play well with jqGrid.
Anyways, my bosses want the first column of the table I created to be fixed, so they can scroll on the x-axis, but still see the first column. How can I modify this plugin to provide this functionality?
Any help is greatly appreciated
Thanks
EDIT:
I tried adding:
th:first-child
{
position : relative;
}
td:first-child
{
position : relative;
}
As well as "fixed", but it seems to be more complicated than this simple solution...
Doing this does have an effect, it just isn't that pleasing. Making this change causes it to stay static on the left side, but I can't really scroll down, and the th doesn't really seem to work.
EDIT 2:
I've begun implementing the solution given below, although I am not entirely confident in my ability to tinker with this plugin. Anyways, here's the current state of tinkering:
I'll continue updating as I go...
I get an error that says this.offset.top is null or not an object... blah,
This code goes in the document.ready thing:
var currentTop = 0;
var currentLeft = 0;
var currentWidth = 0;
var currentHeight = 0;
var currentContent = "";
var currentDiv = "";
var currentID = "";
$('td:first-child').each(function (index) {
currentTop = $(this).offset.top;
currentLeft = $(this).offset.left;
currentWidth = $(this).width;
currentHeight = $(this).height;
currentContent = $(this).html();
currentID = "fixed_column_cell" + index;
currentDiv = "<div class=\"fixed_column_cells\" id=\"" + currentID + "\">" + currentContent + "</div>";
$('body').append(currentDiv);
$('#' + currentID).offset({ top: currentTop, left: currentLeft });
$('#' + currentID).width(currentWidth);
$('#' + currentID).width(currentHeight);
});
$('fixed_column_cells').css('position', 'fixed');
Currently stuck
Interesting problem. I don't think you'll find that the table cell (TD) element is going to want to behave this way for you. The thing to do might be to loop over all the td:first-childs and copy their content into divs of the same size that overlap the TDs. those divs will let you position them correctly.
I think you're running into a limitation of something with inherent table-cell behavior.
Pseduocode:
For each table cell in the first column
get top left position
get width and height
create new div of same size at same position
copy content into new div
set div to fixed pos
I know you said that you have already gotten the jquery library for the fixed header. There are however some libraries out there for fixed columns as well as headers. One that I have used is Fixed Table which allows you to set the left column as well as the headers to be fixed. Hope this will help you

achieving equal height columns in a responsive / flexible layout

I'm trying to achieve equal height columns on a 'responsive' website.
That means I'm using media queries to provide several layouts for one website depending on the size of the device and window.
I have 2 columns which need to have the same height. It's easy to achieve when the layout is fixed. I found dozens of scripts that do it and it works well.
However when I resize the browser window, that generated height doesn't change. So if the window is smaller, the height of the columns stays the same as before and the contents of the columns overflows. It's ugly.
Is there a way that generated height could change as I resize the window ?
Note : because of what's inside the columns I cannot use any CSS trick with backgrounds images etc. I really REALLY need both columns to truly have the same height at all times.
This question is already pretty old, but I didn't stumble upon a good solution for this myself until now.
A working solution would be a jQuery plugin that does something like setting the height of the columns to 'auto', measuring which one is the highest and set the columns to that height. Something along the lines of this:
$.fn.eqHeights = function (options) {
var $el = $(this),
$window = $(window),
opts = $.extend({}, $.fn.eqHeights.defaults, options);
if ($el.length > 0 && !$el.data('eqHeights')) {
$(window).bind('resize.eqHeights', function () {
$el.eqHeights(opts);
});
$el.data('eqHeights', true);
}
return $el.each(function () {
var children = $(this).find(opts.childrenSelector);
if (!(opts.minWidth) || opts.minWidth < $window.width()) {
var curHighest = 0;
children.each(function () {
var $el = $(this),
elHeight = $el.height('auto').height();
if (elHeight > curHighest) {
curHighest = elHeight;
}
}).height(curHighest);
} else {
children.height('auto');
}
});
};
$.fn.eqHeights.defaults = {
childrenSelector: '*',
minWidth: ''
};
You can see this in action here: demo#codepen.io
The plugin supports two options:
childrenSelector: (Optional) The selector by which children that should get equal height are picked. Defaults to *, so everything in your parent is brought to equal height. Set to > to pick only direct children or something else to get the desired effect.
minWidth: (Optional) The minimum viewport width above width the Plugin is working and calculates equal heights for the seleted children. Below their height is set to auto. This comes in handy if at some point your containers are laid out below each other and shouldn't have an equal height. Empty and inactive by default.
While this is working very good in all browser with which I tested, it is a very hackish solution, so maybe someone here can propose something better. I thought about copying the columns and their wrapper to hidden container in the document, but this isn't any less clean and produces a way bigger footprint.
My favorite trick to creating equal height columns that work almost everywhere is to set "overflow:hidden" on a wrapper div, and setting a huge positive bottom padding and a negative bottom margin on the columns themselves. Now the columns will always be the full height of the wrapper, whatever the height of the wrapper is.
Viz -
<div class="wrapper">
<div class="column"> Column one content </div>
<div class="column"> Column two content </div>
</div>
<style type="text/css">
.wrapper {
overflow:hidden;
}
.column {
margin-bottom: -2000px;
padding-bottom: 2000px;
}
</style>
Here's a JSFiddle example - http://jsfiddle.net/yJYTT/
I wrote a small jQuery plugin for this: http://github.com/jsliang/eqHeight.coffee/
Tested it on Chrome, Firefox and IE9 and it works for me.
This works great! To make it work inside of a responsive layout you'll need to add the # media query so it's only used on screen sizes "larger than" your break point. Otherwise, the sidebar color extends down into the main content on the tablet and phone views. Here's how it looks in a responsive stylesheet:
div.wrapper {
overflow:hidden;
}
.column {
background-color: rgba(193,204,164,.5);
padding:2%;
margin-bottom: -2000px;
padding-bottom: 2000px;
}
#media screen and (max-width:960px){
.column {padding-bottom:2%; margin-bottom:0px;}
}
I hacked the solution even further from boundaryfunctions's answer to take into consideration responsive layouts where the panels reflow above each other.
By checking each one against the first one's offset.top I was able to detect the orientation of the panels and resize their .panel-body element or assign an auto heigh for reflowed panels.
(function($) {
$.fn.eqHeights = function() {
var el = $(this);
if (el.length > 0 && !el.data('eqHeights')) {
$(window).bind('resize.eqHeights', function() {
el.eqHeights();
});
el.data('eqHeights', true);
}
var panels = el.find(".panel-body");
var fistoffset = panels.first().offset();
var curHighest = 0;
return panels.each(function() {
var thisoffset = $(this).offset();
var elHeight = $(this).height('auto').height();
if(thisoffset.top==fistoffset.top){
if (elHeight > curHighest) {
curHighest = elHeight;
}
}else{
curHighest = "auto";
}
}).height(curHighest);
};
}(jQuery));
$('.match_all').eqHeights();
Example here: http://bootply.com/render/104399
Some time after the question I know - but for reference - last time I had to solve this problem I hacked this jquery code to a plugin:
http://css-tricks.com/equal-height-blocks-in-rows/
obviously $(window).resize is the crucial part - as it'll re-conform the heights once the re-size has taken place. Taking it a step further I always meant to look into 'de-bouncing' the column reconform to help with performance:
http://paulirish.com/2009/throttled-smartresize-jquery-event-handler/
but never got that far.
I had the same problem. After some research I selected the faux column technique. Check this blog post that I wrote on how to make it work in a responsive design.
Responsive full height (equal height) columns using the faux columns technique

Categories