Make sticky element with overflow hidden expand - html

How can I make a sticky element, with overflow hidden, expand to fill parent element that has scroll on overflow?
A minor example:
.wrap {
position: absolute;
width: 250px;
height: 80px;
overflow: scroll;
background: #844;
}
.stick {
position: sticky;
white-space: nowrap;
overflow: hidden;
background: #484;
top: 0;
}
.text {
color: #ccc;
}
<div class="wrap">
<div class="stick">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 10 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40</div>
<pre class="text">Hello and good bye to the sticky scroll once the wrap area scrolls past the initial width
a
b
c
d
e
f
g
h
</pre>
</div>
Here the green line is sticky, but it does not fill the width of the scroll element, which is what I'm trying to achieve. The effect is visible once one start scrolling. I can solve it by JS, but hoping to do it with CSS.
The sticky element has overflow hidden as it, in real code, contains a wide canvas as a sub element. This element is wider then the content, as in "text" above, and should not be visible beyond the size of "text".
A picture:
The Yellow part is sticky to top: Scroll up / down it stays at top.
The Yellow part scrolls left / right. Follows the blue content left / right.
The Yellow part does not expand the blue part / viewable content AKA overflow hidden.
The Yellow part has full width of blue part
So the yellow sticky should:
Have same width as blue. Anything beyond this hidden
Stay at top of view-port if window scroll up/ down
Follow content left / right
In the real project I use it in a MDI layout with multiple absolute positioned "windows" having one sticky element on top and left of content area. Much like for example when you have a picture open in GIMP. (The pixel bars). Too complex to post code for it, but here is a small mock-up:
The "window" can be resized by dragging element in bottom-right corner.
(function() {
"use strict";
const spacer = {
el: null,
sz: {
small: [100, 100],
wide: [1000, 100],
high: [100, 1000],
big: [1000, 1000]
},
change: function (ev) {
let z = spacer.sz[ev.target.value];
spacer.el.style.width = z[0] + 'px';
spacer.el.style.height = z[1] + 'px';
},
init: function () {
spacer.el = document.querySelector('.content-spacer');
spacer.el.addEventListener('change', spacer.change);
}
};
const resizer = {
el: null,
overlay: null,
move: function (ev) {
ev.preventDefault();
ev.stopPropagation();
resizer.el_rz.style.marginTop = (ev.layerY - resizer.pos[0]) + 'px';
resizer.el_rz.style.marginLeft = (ev.layerX - resizer.pos[1]) + 'px';
},
end: function () {
window.removeEventListener('mousemove', resizer.move);
window.removeEventListener('mouseup', resizer.end);
resizer.overlay.style.display = 'none';
},
start: function (ev) {
let t = ev.target,
cs = getComputedStyle(t)
;
ev.preventDefault();
ev.stopPropagation();
resizer.el_rz = ev.target;
resizer.pos = [
ev.clientX - (parseInt(cs.marginLeft) || 0),
ev.clientY - (parseInt(cs.marginTop) || 0),
];
window.addEventListener('mouseup', resizer.end);
window.addEventListener('mousemove', resizer.move);
resizer.overlay.style.display = 'block';
},
init: function () {
resizer.el = document.getElementById("sizer");
resizer.overlay = document.getElementById("overlay");
resizer.el.addEventListener('mousedown', resizer.start);
}
};
function fill_numbers(el) {
let i, s = '';
for (i = 1; i < 500; ++i)
s += (i % 10) + ' ';
el.textContent = s;
}
resizer.init();
spacer.init();
fill_numbers(document.querySelector('.top-line-c'));
fill_numbers(document.querySelector('.left-line-c'));
})();
body {
position : relative;
width : 100vw;
height : 100%;
padding : 0;
margin : 0;
}
#overlay {
position : absolute;
display : none;
z-index : 100;
height : 100vh;
width : 100vw;
opacity : 0.1;
background : #333;
z-index : 100;
cursor : move;
}
.window {
position : absolute;
overflow : hidden;
margin-top : 5px;
margin-left : 5px;
min-width : 10px;
min-height : 10px;
background : #988;
padding : 0;
}
.sizer {
position : relative;
background : #8a8;
bottom : 0;
right : 0;
width : 15px;
height : 15px;
margin-top : 120px;
margin-left : 250px;
cursor : move;
}
.wrap {
position : relative;
background : #565;
width : 100%;
height : 100%;
}
.content-outer {
overflow : hidden;
top : 0;
bottom : 0;
right : 0;
left : 0;
position : absolute;
background : #a77;
padding : 0;
}
.corner {
position : sticky;
width : 25px;
height : 25px;
top : 0;
left : 0;
z-index : 150;
background : red;
}
.top-line {
top : 0;
margin-top : -25px;
margin-left : 25px;
position : sticky;
height : 25px;
background : pink;
width : 100%;
white-space : nowrap;
z-index : 100;
overflow : hidden;
}
.left-line {
position : sticky;
max-height : 100%;
overflow : hidden;
background : #494;
width : 25px;
left : 0;
z-index : 100;
text-align : center;
}
.left-line-c {
height : 5000px;
}
.content-inner {
position : absolute;
overflow : scroll;
top : 25px;
right : 0;
bottom : 0;
left : 0;
}
.content-text {
position : absolute;
top : 0;
left : 0;
right : 0;
bottom : 0;
margin : 25px ;
}
.content-spacer {
background: rgb(186,124,13);
background: linear-gradient(90deg, rgba(186,124,13,1) 0%, rgba(112,25,58,0.9037815809917717) 53%, rgba(181,0,255,1) 100%);
margin : 15px;
width : 1000px;
height : 100px;
}
ul {
list-style : none;
margin : 0;
padding : 0;
}
.header {
background : #000;
color : #aaa;
height : 25px;
}
<div id="overlay"></div>
<div class="wrap">
<div class="window">
<div class="content-outer">
<div class="header"><span>Header</span></div>
<div class="content-inner">
<div class="corner">C</div>
<div class="top-line">
<div class="top-line-c"></div>
</div>
<div class="left-line">
<div class="left-line-c"></div>
</div>
<div class="content-text">
<div class="content-spacer">
<ul>
<li><label><input type="radio" name="sp" value="small">small</label></li>
<li><label><input type="radio" name="sp" value="wide" checked>wide</label></li>
<li><label><input type="radio" name="sp" value="high">high</label></li>
<li><label><input type="radio" name="sp" value="big">big</label></li>
</ul>
</div>
</div>
</div>
</div>
<div class="sizer" id="sizer" tabindex="0"></div>
</div>
</div>

See if that is what you're needing :
.wrap {
position: absolute;
width: 250px;
height: 80px;
overflow: scroll;
background: #844;
}
.stick {
position: sticky;
display: table;
white-space: nowrap;
overflow: hidden;
background: #484;
}
.text {
color: #ccc;
}
<div class="wrap">
<div class="stick">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 10 21 22 23 24 25 26 27 28 29 30 31 32 33</div>
<pre class="text">Hello and good bye to the sticky scroll once the wrap area scrolls past the initial width</pre>
</div>
This is going to expand the child <div> to the total content of the parent <div>.
Below the same example with the second snippet attached :
(function() {
"use strict";
const spacer = {
el: null,
sz: {
small: [100, 100],
wide: [1000, 100],
high: [100, 1000],
big: [1000, 1000]
},
change: function (ev) {
let z = spacer.sz[ev.target.value];
spacer.el.style.width = z[0] + 'px';
spacer.el.style.height = z[1] + 'px';
},
init: function () {
spacer.el = document.querySelector('.content-spacer');
spacer.el.addEventListener('change', spacer.change);
}
};
const resizer = {
el: null,
overlay: null,
move: function (ev) {
ev.preventDefault();
ev.stopPropagation();
resizer.el_rz.style.marginTop = (ev.layerY - resizer.pos[0]) + 'px';
resizer.el_rz.style.marginLeft = (ev.layerX - resizer.pos[1]) + 'px';
},
end: function () {
window.removeEventListener('mousemove', resizer.move);
window.removeEventListener('mouseup', resizer.end);
resizer.overlay.style.display = 'none';
},
start: function (ev) {
let t = ev.target,
cs = getComputedStyle(t)
;
ev.preventDefault();
ev.stopPropagation();
resizer.el_rz = ev.target;
resizer.pos = [
ev.clientX - (parseInt(cs.marginLeft) || 0),
ev.clientY - (parseInt(cs.marginTop) || 0),
];
window.addEventListener('mouseup', resizer.end);
window.addEventListener('mousemove', resizer.move);
resizer.overlay.style.display = 'block';
},
init: function () {
resizer.el = document.getElementById("sizer");
resizer.overlay = document.getElementById("overlay");
resizer.el.addEventListener('mousedown', resizer.start);
}
};
function fill_numbers(el) {
let i, s = '';
for (i = 1; i < 500; ++i)
s += (i % 10) + ' ';
el.textContent = s;
}
resizer.init();
spacer.init();
fill_numbers(document.querySelector('.top-line-c'));
fill_numbers(document.querySelector('.left-line-c'));
})();
body {
position : relative;
width : 100vw;
height : 100%;
padding : 0;
margin : 0;
}
#overlay {
position : absolute;
display : none;
z-index : 100;
height : 100vh;
width : 100vw;
opacity : 0.1;
background : #333;
z-index : 100;
cursor : move;
}
.window {
position : absolute;
overflow : hidden;
margin-top : 5px;
margin-left : 5px;
min-width : 10px;
min-height : 10px;
background : #988;
padding : 0;
}
.sizer {
position : relative;
background : #8a8;
bottom : 0;
right : 0;
width : 15px;
height : 15px;
margin-top : 120px;
margin-left : 250px;
cursor : move;
}
.wrap {
position : relative;
background : #565;
width : 100%;
height : 100%;
}
.content-outer {
overflow : hidden;
top : 0;
bottom : 0;
right : 0;
left : 0;
position : absolute;
background : #a77;
padding : 0;
}
.corner {
position : sticky;
width : 25px;
height : 25px;
top : 0;
left : 0;
z-index : 150;
background : red;
}
.top-line {
top : 0;
margin-top : -25px;
margin-left : 25px;
position : sticky;
height : 25px;
background : pink;
width : 100%;
display: table;
white-space : nowrap;
z-index : 100;
overflow : hidden;
}
.left-line {
position : sticky;
max-height : 100%;
overflow : hidden;
background : #494;
width : 25px;
left : 0;
z-index : 100;
text-align : center;
}
.left-line-c {
height : 5000px;
}
.content-inner {
position : absolute;
overflow : scroll;
top : 25px;
right : 0;
bottom : 0;
left : 0;
}
.content-text {
position : absolute;
top : 0;
left : 0;
right : 0;
bottom : 0;
margin : 25px ;
}
.content-spacer {
background: rgb(186,124,13);
background: linear-gradient(90deg, rgba(186,124,13,1) 0%, rgba(112,25,58,0.9037815809917717) 53%, rgba(181,0,255,1) 100%);
margin : 15px;
width : 1000px;
height : 100px;
}
ul {
list-style : none;
margin : 0;
padding : 0;
}
.header {
background : #000;
color : #aaa;
height : 25px;
}
<div id="overlay"></div>
<div class="wrap">
<div class="window">
<div class="content-outer">
<div class="header"><span>Header</span></div>
<div class="content-inner">
<div class="corner">C</div>
<div class="top-line">
<div class="top-line-c"></div>
</div>
<div class="left-line">
<div class="left-line-c"></div>
</div>
<div class="content-text">
<div class="content-spacer">
<ul>
<li><label><input type="radio" name="sp" value="small">small</label></li>
<li><label><input type="radio" name="sp" value="wide" checked>wide</label></li>
<li><label><input type="radio" name="sp" value="high">high</label></li>
<li><label><input type="radio" name="sp" value="big">big</label></li>
</ul>
</div>
</div>
</div>
</div>
<div class="sizer" id="sizer" tabindex="0"></div>
</div>
</div>
I just added display: table; on the .top-line class.

Related

Header position offset not following top margin of container

Here I have a header with position: fixed. As it does not go with the normal flow of the window, a margin for the body is set to the height of the header (here 100px). Now, the body starts right after the bottom of the header.
The main div in the body has a margin-top of 50px. But, the header grasps that margin, and it's not shown. If I set a border on the body, then the margin is shown. I don't know what is the relation of that top margin with the border of the body.
This can be solved if I add 50px more to the margin-top of the main div. But I want to know what's happening here.
body {
background-color: white;
margin-top: 100px;
/* border: 1px solid black; */
}
header {
background-color: black;
height: 100px;
position: fixed;
z-index: 1;
top: 0;
left: 0;
right: 0;
}
main {
background-color: gray;
margin-top: 50px;
width: 100%;
height: 100vh;
}
<header></header>
<main></main>
Adding a border adjusts the display of the layout because the <body> and the <main> margins overlap without the border (since it's just whitespace), but with the border rendered, the two margins must be separate. Thus, without the border, the total margin is 100px, and with the border, the total margin is 150px.
See demo below. (I've also added a button to hide the <header> since it's position is fixed, so it isn't relevant to the situation.
const body = document.querySelector("body");
const header = document.querySelector("header");
const a = document.createElement("div");
const b1 = document.createElement("button");
b1.textContent = "Toggle body border";
b1.addEventListener("click", () => {
if (body.style.border !== "1px solid red") {
body.style.border = "1px solid red";
} else {
body.style.border = "none";
}
});
const b2 = document.createElement("button");
b2.textContent = "Toggle body margin";
b2.addEventListener("click", () => {
if (body.style.marginTop !== "0px") {
body.style.marginTop = "0px";
} else {
body.style.marginTop = "100px";
}
});
const b3 = document.createElement("button");
b3.textContent = "Toggle header visibility";
b3.addEventListener("click", () => {
if (header.style.display !== "none") {
header.style.display = "none";
} else {
header.style.display = "block";
}
});
a.appendChild(b1);
a.appendChild(b2);
a.appendChild(b3);
a.style.position = "fixed";
a.style.top = "0";
a.style.zIndex = "2";
document.body.appendChild(a);
body {
background-color: white;
margin-top: 100px;
}
header {
background-color: black;
height: 100px;
position: fixed;
z-index: 1;
top: 0;
left: 0;
right: 0;
}
main {
background-color: gray;
margin-top: 50px;
width: 100%;
height: 100vh;
}
<header></header>
<main></main>

How to create a splitted page with transitions

I`m trying to create a page, splitted horizontally or vertically. I want a nice transition between pages, splitted differently.
My solution is a background element with transform:rotateZ(0 or 90deg) and flex container with two elements:
<template>
<div id="app">
<div id="split-page-bg" :class="['split-' + splitType]"></div>
<div id="split-page" :style="{ 'flex-direction': flexDirection }">
<div id="split-page-part-first">
<p>Content #1</p>
<button #click="switchSplitType">{{ buttonText }}</button>
</div>
<div id="split-page-part-second">
<p>Content #2</p>
</div>
</div>
</div>
</template>
<script>
export default {
computed: {
buttonText() {
return (
"Switch to " +
(this.splitType === "horizontal" ? "vertical" : "horizontal")
);
},
flexDirection() {
return this.splitType === "horizontal" ? "column" : "row";
}
},
data() {
return {
splitType: "horizontal"
};
},
methods: {
switchSplitType() {
this.splitType =
this.splitType === "horizontal" ? "vertical" : "horizontal";
}
}
};
</script>
<style>
#app {
font-family: sans-serif;
font-size: 3rem;
height: 100vh;
width: 100vw;
padding: 0;
margin: 0;
}
#split-page-bg {
--w: max(200vw, 200vh);
--offset-percentage-vertical: 50vh;
--offset-percentage-horizontal: 50vw;
top: calc(-0.5 * var(--w) + 100vh - var(--offset-percentage-vertical));
left: calc(-0.5 * var(--w) + 100vw - var(--offset-percentage-horizontal));
width: var(--w);
height: var(--w);
position: fixed;
z-index: -10;
transition: transform 0.3s ease;
background: linear-gradient(0deg, #ff7d00 50%, #15616d 0%);
}
.split-horizontal {
--offset-percentage-vertical: 50vh;
}
.split-vertical {
transform: rotateZ(90deg);
}
#split-page {
width: 100vw;
height: 100vh;
display: flex;
}
#split-page-part-first {
flex: 0 1 50%;
}
#split-page-part-second {
flex: 0 1 50%;
}
button {
font: inherit;
}
</style>
Codepen
But it`s hard to work with separate background element. Sometimes background does not match with containers, there is a 1-2 px difference in width/height.
Question is, is there a better way for implementing this? Can I somehow animate containers like this and work with them in developer-friendly way?

Div height based on rendered character size

Essentially I'm wondering whether there's any mechanism to map your font's characters to a div's dimensions, taller letters creating taller divs, etc
There's units like em, ex, and ch that are supposed to represent font size but these are the font as a whole, not relative to the present characters
This may just not be possible within css/html
I've created this code example of what I mean, red being the div bounds, blue being my desired output
body {
margin : 5px 0 0 5px;
padding : 0;
display : inline-flex;
flex-flow : column;
}
.set {
display : inline-flex;
flex-flow : row;
padding : 0;
}
.overlay {
position : absolute;
margin : 5px 0px 5px 5px;
width : 60px;
box-shadow : 0 0 0 2px blue;
}
.text {
margin : 5px 5px 5px 5px;
font-family : sans-serif;
font-size : 32px;
line-height : 32px;
box-shadow : 0 0 0 2px red;
width : 60px;
text-align : center;
}
.overlay.overlay-1 {
margin-top : -26px;
height : 13px;
}
.overlay.overlay-2 {
margin-top : -34px;
height : 22px;
}
.overlay.overlay-3 {
margin-top : -28px;
height : 23px;
}
<html>
<body>
<div class="set">
<div>
<div class="text">xxx</div>
</div>
<div>
<div class="text">xxx</div>
<div class="overlay overlay-1"></div>
</div>
</div>
<div class="set">
<div>
<div class="text">Xxx</div>
</div>
<div>
<div class="text">Xxx</div>
<div class="overlay overlay-2"></div>
</div>
</div>
<div class="set">
<div>
<div class="text">xxy</div>
</div>
<div>
<div class="text">xxy</div>
<div class="overlay overlay-3"></div>
</div>
</div>
</body>
</html>
It seems like there is no programatic way of achieving this with CSS, as the relative heights in CSS refer to the font dimensions, not individual characters.
Edit: found a Javascript code that converts a line of text into an image, then measures the image size in pixels. Maybe you can use that.
Check this post
function measureTextHeight(fontSizeFace) {
// create a temp canvas
var width = 1000;
var height = 60;
var canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext("2d");
// Draw the entire a-z/A-Z alphabet in the canvas
var text = "o";
ctx.save();
ctx.font = fontSizeFace;
ctx.clearRect(0, 0, width, height);
ctx.fillText(text, 0, 40);
ctx.restore();
// Get the pixel data from the canvas
var data = ctx.getImageData(0, 0, width, height).data,
first = false,
last = false,
r = height,
c = 0;
// Find the last line with a non-transparent pixel
while (!last && r) {
r--;
for (c = 0; c < width; c++) {
if (data[r * width * 4 + c * 4 + 3]) {
last = r;
break;
}
}
}
// Find the first line with a non-transparent pixel
while (r) {
r--;
for (c = 0; c < width; c++) {
if (data[r * width * 4 + c * 4 + 3]) {
first = r;
break;
}
}
// If we've got it then return the height
if (first != r) return last - first;
}
// error condition if we get here
return 0;
}

Contain fixed element within container

Having a flex layout with top, left, middle and right.
Middle is divided into main and foot.
Within the main I want to have fixed elements, kind of like an MDI, as well as static elements.
If one scroll the fixed element should stay in same position of view. But, it should be contained within the main element if it is moved above or to the left of main. As in: not overlap the top, left, right etc.
THIS:
Colors and margins added to make a visual representation of the layout
NOT THIS:
Below is a simplified sample with a container within a container.
If one select the fixed positioning for the sub "window" it stay in place on scrolling, but it overlaps the parent if moved outside.
I can use absolute and reposition it on scroll by using JavaScript, but wondered if there was a pure CSS / layout way to get the same result.
function set_style_pos (e) {
moveable.style.position = e.target.value;
}
function halt (e) {
e.preventDefault();
e.stopPropagation();
}
const drag = {
el: null,
ex: 0,
ey: 0,
xs: 0,
ys: 0,
move: function (e) {
halt(e);
drag.el.style.marginLeft = (e.clientX - drag.sx + drag.ex) + 'px';
drag.el.style.marginTop = (e.clientY - drag.sy + drag.ey) + 'px';
},
end: function (e) {
halt(e);
window.removeEventListener('mouseup', drag.end);
window.removeEventListener('mousemove', drag.move);
},
start: function (e) {
let cs;
halt(e);
window.addEventListener('mouseup', drag.end);
window.addEventListener('mousemove', drag.move);
drag.el = e.target;
cs = getComputedStyle(drag.el);
drag.ex = parseInt(cs.getPropertyValue('margin-left')) || 0;
drag.ey = parseInt(cs.getPropertyValue('margin-top')) || 0;
drag.sx = e.clientX;
drag.sy = e.clientY;
},
check: function (e) {
let t = e.target;
if (t.dataset.moveable == "1")
drag.start(e);
}
};
document.addEventListener('mousedown', drag.check);
document.addEventListener('change', set_style_pos);
lines.textContent = "scroll me\n".repeat(100);
* {
box-sizing: border-box;
}
body {
margin: 0;
height: 100vh;
display: flex;
flex-direction: column;
color: #444;
font: 14px sans-serif;
}
label {
cursor: pointer;
}
.outer {
display: flex;
padding: 20px;
background: goldenrod;
flex-grow: 1;
overflow: hidden;
}
.inner {
position: relative;
overflow: scroll;
background: gray;
flex-grow: 1;
}
.box {
position: absolute;
width: 140px;
height: 150px;
background: silver;
box-shadow: 0 0 3px red;
cursor: move;
margin-left: 90px;
margin-top: -5px;
padding: 20px;
}
.box div {
font-weight: 700;
pointer-events: none;
text-align: center;
}
<div class="outer">
<div class="inner">
<div class="box" id="moveable" data-moveable="1">
<div>Move Me</div><br />
<label><input type="radio" name="p" value="absolute" checked />absolute</label><br />
<label><input type="radio" name="p" value="fixed" />fixed</label>
</div>
<pre id="lines"></pre>
</div>
</div>
Just use z-index.
Example:
function set_style_pos (e) {
moveable.style.position = e.target.value;
}
function halt (e) {
e.preventDefault();
e.stopPropagation();
}
const drag = {
el: null,
ex: 0,
ey: 0,
xs: 0,
ys: 0,
move: function (e) {
halt(e);
drag.el.style.marginLeft = (e.clientX - drag.sx + drag.ex) + 'px';
drag.el.style.marginTop = (e.clientY - drag.sy + drag.ey) + 'px';
},
end: function (e) {
halt(e);
window.removeEventListener('mouseup', drag.end);
window.removeEventListener('mousemove', drag.move);
},
start: function (e) {
let cs;
halt(e);
window.addEventListener('mouseup', drag.end);
window.addEventListener('mousemove', drag.move);
drag.el = e.target;
cs = getComputedStyle(drag.el);
drag.ex = parseInt(cs.getPropertyValue('margin-left')) || 0;
drag.ey = parseInt(cs.getPropertyValue('margin-top')) || 0;
drag.sx = e.clientX;
drag.sy = e.clientY;
},
check: function (e) {
let t = e.target;
if (t.dataset.moveable == "1")
drag.start(e);
}
};
document.addEventListener('mousedown', drag.check);
document.addEventListener('change', set_style_pos);
lines.textContent = "scroll me\n".repeat(100);
* {
box-sizing: border-box;
}
body {
margin: 0;
height: 100vh;
display: flex;
flex-direction: column;
color: #444;
font: 14px sans-serif;
}
label {
cursor: pointer;
}
.outer {
display: flex;
padding: 20px;
background: goldenrod;
/*flex-grow:1; Disable to control the height for presentaion*/
height:200px !important;
overflow:hidden; /*to hide scrollme lines*/
}
.inner {
position: relative;
overflow: scroll;
background: gray;
flex-grow: 1;
}
.box {
position: absolute;
width: 140px;
height: 150px;
background: silver;
box-shadow: 0 0 3px red;
cursor: move;
margin-left: 90px;
margin-top: -5px;
padding: 20px;
}
.box div {
font-weight: 700;
pointer-events: none;
text-align: center;
}
.prevent{
width:200px;
height:200px;
display:flex;
background-color:blue;
color:white;
justify-content:center;
align-items:center;
font-weight:bold;
/*--The solution--*/
z-index:1;
}
<div class="outer">
<div class="inner">
<div class="box" id="moveable" data-moveable="1">
<div>Move Me</div><br />
<label><input type="radio" name="p" value="absolute" checked />absolute</label><br />
<label><input type="radio" name="p" value="fixed" />fixed</label>
</div>
<pre id="lines"></pre>
</div>
</div>
<div class="prevent">
Prevent overlap
</div>
I hope this helps.
Use a sticky container and let children be absolute.
Had tested with z-index, all over, before posting but had not found any satisfactory solution that way.
I also tried various with position: sticky, and there is where I found the solution at last :)
One can wrap the sub windows in a sticky container which is positioned top left of the main container.
Pros:
Simple
Fairly clean HTML structure
The window stay below scroll-bars of container
Positioning relative to content wrapper
Cons:
If one want to make it non-fixed / non-sticy one have to move the element to parent and vice versa.
Absolute positioned children will not expand the container – thus not rearranging the DOM flow. (Which was the issue on earlier attempts using sticky).
Tested in FireFox, Chrome, Vivaldi, Opera Mini and Opera.
The core of it:
<div class="outer">
<div class="main">
<div class="wrap-sticky">
<div class="sub-window">
Fixed Window
</div>
</div>
Other "normal" content
</div>
</div>
And:
.outer {
overflow: hidden;
}
.main {
position: relative;
overflow: scroll;
}
.wrap-sticky {
position: sticky;
top: 0;
left: 0;
}
.sub-window {
position: absolute;
}
function get_pos (el) {
let cs = getComputedStyle(el);
return [
parseInt(cs.getPropertyValue('left')) || 0,
parseInt(cs.getPropertyValue('top')) || 0
];
}
function set_style_pos (e) {
let [x, y] = get_pos (moveable);
if (e.target.value == "sticky") {
wrap_sticky.appendChild(moveable);
moveable.style.left = (x - inner.scrollLeft) + 'px';
moveable.style.top = (y - inner.scrollTop) + 'px';
} else {
inner.appendChild(moveable);
moveable.style.left = (x + inner.scrollLeft) + 'px';
moveable.style.top = (y + inner.scrollTop) + 'px';
}
}
function halt (e) {
e.preventDefault();
e.stopPropagation();
}
const drag = {
el: null,
ex: 0,
ey: 0,
xs: 0,
ys: 0,
move: function (e) {
halt(e);
drag.el.style.left = (e.clientX - drag.sx + drag.ex) + 'px';
drag.el.style.top = (e.clientY - drag.sy + drag.ey) + 'px';
},
end: function (e) {
halt(e);
window.removeEventListener('mouseup', drag.end);
window.removeEventListener('mousemove', drag.move);
},
start: function (e) {
halt(e);
window.addEventListener('mouseup', drag.end);
window.addEventListener('mousemove', drag.move);
drag.el = e.target;
[drag.ex, drag.ey] = get_pos(drag.el);
drag.sx = e.clientX;
drag.sy = e.clientY;
},
check: function (e) {
let t = e.target;
if (t.dataset.moveable == "1")
drag.start(e);
}
};
document.addEventListener('mousedown', drag.check);
document.addEventListener('change', set_style_pos);
lines.textContent = "scroll me\n".repeat(100) + "horiz".repeat(100) + 'END';
* {
box-sizing: border-box;
}
body {
margin: 0;
height: 100vh;
display: flex;
flex-direction: column;
color: #444;
font: 14px sans-serif;
}
label {
cursor: pointer;
}
.outer {
display: flex;
padding: 20px;
background: goldenrod;
flex-grow: 1;
overflow: hidden;
}
.inner {
position: relative;
overflow: scroll;
background: gray;
flex-grow: 1;
}
.box {
position: absolute;
width: 160px;
height: 100px;
background: silver;
box-shadow: 0 0 3px red;
cursor: move;
padding: 20px;
top: 20px;
left: 20px;
}
.box div {
font-weight: 700;
pointer-events: none;
text-align: center;
}
.wrap-sticky {
position: sticky;
top: 0;
left: 0;
}
<div class="outer">
<div class="inner" id="inner">
<div class="wrap-sticky" id="wrap_sticky">
<div class="box" id="moveable" data-moveable="1">
<div>Drag & Move Me</div>
<label><input type="radio" name="p" value="sticky" checked />In sticky</label><br />
<label><input type="radio" name="p" value="absolute" />In main</label>
</div>
</div>
<pre id="lines"></pre>
</div>
</div>

How to change data visible range to % percent

I am using this for my header that changes in a one page scroll up and down page. I noticed that it's not responsive so i am asking you if you maybe know a way to make that responsive. Like changing the 0-690 into a percentage so that it will work on mobile and also on a tv screen.
HTML
<div class="header header-1" data-visible-range="0-690">Portfolio</div>
<div class="header header-2" data-visible-range="691-2100">Services</div>
<div class="header header-3" data-visible-range="2101-">Contact</div>
CSS
.header-1 {
background-color:dimgray;
display: block;
}
.header-2 {
background-color:dimgray;
}
.header-3 {
background-color:dimgray;
}
.header {
position: fixed;
top: 0;
left: 0;
height:8vmax;
width: 100%;
display: none;
visibility:hidden;
transition: visibility .4s, opacity .4s ease-in-out;opacity:0;
font-size:4vmax;padding:1.58vmax;color:white;
}
What if, instead of basing it off pixels, you just checked to see if an element hit the top of the page, and then changed the header?
We'll call these elements "triggers." See my code below for an example of how they work.
let updateHeader = () => {
let scrollTop = $(window).scrollTop(),
triggerTitle = "Hi";
$('.trigger').each((i, el) => {
let topPos = $(el).offset().top,
distance = topPos - scrollTop;
if (distance < 0)
triggerTitle = $(el).data('title');
});
$('header h2').text(triggerTitle);
}
$(window).scroll(updateHeader);
$(window).on('touchmove', updateHeader);
body {
margin: 0;
}
#container {
height: 1000px;
}
header {
width: 100%;
position: fixed;
top: 0;
background-color: red;
}
p {
margin: 200px 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="container">
<header><h2>Hi</h2></header>
<p class="trigger" data-title="section1">
trigger1
</p>
<p class="trigger" data-title="section2">
trigger2
</p>
<p class="trigger" data-title="section3">
trigger3
</p>
</div>
As you scroll down the page, each trigger hits the top of the page, and the text in the header will change to the the value of the latest trigger's data-title. You could position these triggers appropriately above each of your website's sections, so that, no matter what size the screen, the header should update at the right time. Here's a codepen.
EDIT
Try this JS instead for maximum compatibility (no es6 involved).
function updateHeader() {
var scrollTop = $(window).scrollTop(),
triggerTitle = "Hi";
$('.trigger').each(function(i, el) {
var topPos = $(el).offset().top,
distance = topPos - scrollTop;
if (distance < 0)
triggerTitle = $(el).data('title');
});
$('header h2').text(triggerTitle);
}
$(window).scroll(updateHeader);
$(window).on('touchmove', updateHeader);