4

I'm going to guess the answer to this question will be "no," but it would be so nice, I'm going to ask anyways.

What I'm trying to do is freeze an element inside a scrollable DIV such that it stays in place vertically. This is to implement a frozen row feature in a table.

It's pretty easy to do with JavaScript and absolute positioning. Here's the HTML for a container and three inner DIVs (see here for the live version):

<div id="container">
  <div id="background">
    Content
  </div>
  <div id="absolutediv">
    Absolute stays inside
  </div>
  <div id="fixeddiv">
    Fixed escapes!
  </div>
  <div id="absolutediv2">
    Stays put!
  </div>
</div>

The relevant CSS:

#container {
  position: fixed;
  left: 20px;
  width: 250px;
  height: 250px;
  top: 20px;
  overflow: scroll;
}

#absolutediv {
  position: absolute;
  top: 30px;
  width: 300px;
  background-color: #CEC;
}

#fixeddiv {
  position: fixed;
  top: 100px;
  width: 300px;
  background-color: #ECC;
}

#absolutediv2 {
  position: absolute;
  width: 300px;
  top: 120px;
  background-color: #ECC;
}

And JavaScript that will hold #absolutediv2 in place:

var div = document.getElementById('absolutediv2');
var container = document.getElementById('container');

container.addEventListener('scroll', function() {
  div.style.top = container.scrollTop + 120 + 'px';
});

So #absolutediv2 is behaving the way I want. But look at #fixeddiv. This gets close to what I'm after, and I suspect it looks nicer on mobile devices because the browser can hold it in place without waiting to run the script. Except that it (a) runs right over the borders, and (b) doesn't scroll horizontally.

Is there any way to get what I'm after with pure CSS, something that would run well on a mobile browser?

(In my page, one way to do this would be to place the frozen row above the container DIV, but the number of frozen rows changes depending on where the user has scrolled to, meaning that the container DIV would have to move around.)

Edit:

To sum up, I want a div that:

  1. Scrolls horizontally with its container
  2. Stays put when its container scrolls vertically
  3. Looks like it belongs to its container
  4. Looks nice on a mobile browser

The last one is the tricky bit. I can achieve #1, #2, and #3 with an absolute-position div and JavaScript, but it looks ugly on a mobile browser because it lags. Using a fixed-position div, I can get #2 and #4, and I can achieve #1 with JavaScript (the lag doesn't bother me so much horizontally), but not #3, because a fixed-position div suddenly sits on top of its container.

Google has a suggestion for this kind of thing, but it's a pretty extreme solution: https://developers.google.com/mobile/articles/webapp_fixed_ui

mrdecemberist
  • 2,631
  • 2
  • 20
  • 23
  • Do you really want a pure CSS solution, or just a solution that would work in a mobile browser? – Jeff May 08 '13 at 16:15
  • You're right. I'd rather have something that works in a mobile browser. I just checked the JavaScript solution on an iPad, and it's ugly. Lags way behind the scroll event. – mrdecemberist May 08 '13 at 16:28
  • You've probably already thought of it but you could just create an external element outside of the box and then just position it over the place where the original element was inside of the box. Then hide the original element. That way the only javascript that has to run is just the setup. Then you won't have to worry about it lagging behind – SnareChops May 08 '13 at 16:33
  • That's pretty much what #fixeddiv is. The problem is that it overruns the borders. I could constrain it to the size of the container element, but it will still paint on top of the scroll bars. – mrdecemberist May 08 '13 at 16:52
  • I have found [this article](https://developers.google.com/mobile/articles/webapp_fixed_ui) which suggests overriding the browser's scroll features entirely and using transform3d to scroll elements, allowing me to update the screen all at once and avoid the lag. Not keen to try it, but if I have to, I have to. – mrdecemberist May 08 '13 at 16:55
  • If the below answer presented a solution to your question, please accept the answer by clicking on the check mark beside the answer. This will help future users searching for an answer to the same question. Thank you. – SnareChops Oct 06 '14 at 02:45

2 Answers2

3

Ok, I haven't tested this but it should be along the right track. Basically this gives you the ability to create multiple "Sticker" items with the HTML5 data attribute I created for you data-special="sticker". The jQuery looks for these, then copies the data and appends it to another <div> element that is positioned where the original was, then it hides the original.

#container {
    position: fixed;
    left: 20px;
    width: 250px;
    height: 250px;
    top: 20px;
    overflow: scroll;
}

#original-element {
    position: absolute;
    top: 30px;
    width: 300px;
    background-color: #CEC;
}

.sticker {
    position:absolute;
}    

<div id="wrapper">
    <div id="container">
        <div id="background">
           Content
        </div>
        <div id="original-element" data-special="sticker">
           I want to stay put!
        </div>
    </div>
</div>

$("[data-special='sticker']").each(function () {
    $('#wrapper').append(
        $('<div/>').html($(this).html())
                   .addClass("sticker")
                   .css('top', parseInt($('#container').css('top')) + parseInt($(this).css('top')))
                   .css('left', $('#container').css('left'))
                   .css('width', $('#container').css('width'))
                   .css('background-color', $(this).css('background-color'))
    );
    $(this).css('display', "none");
});    

Let me know how it works for you, also one downside to this is once the original element is hidden, the space it used to take up is then collapsed... I'll try to brainstorm a solution for that.

Edit:

Changed the JS to get the #container width instead of the original element width as the original element is larger that the container.

Edit:

Tested: jsfiddle

Some issues would be that the element will then also overlap the scroll bar, if you knew the width of that you could then subtract if from the value.

Also check the updated code above. There were some errors...

SnareChops
  • 13,175
  • 9
  • 69
  • 91
  • I patched up your answer into a JSFiddle: http://jsfiddle.net/U7Ayt/. The element very much stays put, but it doesn't look like it belongs to the container, and it doesn't scroll horizontally with it. – mrdecemberist May 08 '13 at 17:14
  • @fluggo Yeah, you beat me to it. My jsfiddle is above as well. I worked out a few of the kinks, but the not scrolling with the object and the overlap of the scroll bar are some setbacks... You might be able to put the new element in a scrollable div and then programmatically scroll both together... but at that point it would probably be worth it to use the other method... I wonder if there would be a way to speed up the original calculation you had so that it wouldn't lag. – SnareChops May 08 '13 at 17:18
1

You might want to have a look at the following post:

As explained in this answer:

A script-free alternative is position: sticky, which is supported in Chrome, Firefox, and Safari. See the article on HTML5Rocks and demo, and Mozilla docs.

As of today, the demo linked works for me in Firefox but not in Chrome.

Community
  • 1
  • 1
J0ANMM
  • 7,849
  • 10
  • 56
  • 90
  • According to [this video](https://youtu.be/0AmMBjZ6tdY?t=7m11s), for safari you need `position: -webkit-sticky` – J0ANMM Jan 11 '17 at 15:54