I got problem with positioning 2 divs - I don't know which one will have longer. When my #rightcontent div is longer than #leftcontent i want to see end of content in #leftcontent stays at bottom of screen, like on this image:
And here is a code snippet:
for(var i=6000;i--;){
$('#rightcontent').append(i+ ' ');
$('#leftcontent').append("i ");
}
#font-face {
font-family: Gill Sans MT;
src: url("Gill Sans MT.ttf");
}
body{
margin-top: 0px;
margin-left: 0px;
margin-right: 0px;
margin-bottom: 0px;
}
#header{
position: fixed;
text-align: center;
font-size: 32px;
background: #f4f4e6;
padding-top: 2px;
min-width: 100%;
min-height: 28px;
}
#content{
padding-top: 38px;
text-align: center;
}
#footer{
position: fixed;
text-align: center;
background: #f4f4e6;
min-width: 100%;
font-style: italic;
font-family: Gill Sans MT;
letter-spacing: -1;
}
#rightcontent{
float: right;
max-width: 55%;
padding-bottom: 20px;
}
#leftcontent{
padding-bottom: 20px;
max-width: 45%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="header">
Worki do odkurzaczy
</div>
<script>
document.write("<div id=content width=" + document.documentElement.clientWidth + "px height=" + (document.documentElement.clientHeight - 64) + "px style=padding-bottom:20px;min-height:" + (document.documentElement.clientHeight - 52) + "px; data-scroll-offset=28>");
</script>
<div id="rightcontent" name="target">
</div>
<div id="leftcontent">
</div>
</div>
<script>
document.write("<div id=footer style=top:" + (document.documentElement.clientHeight - 22) + "px;>");
//IDK WHY ON LOCAL TEST IS HERE -32px
</script>
Post Scriptum
I found a quiet simple answer here but I don't get this color example.
The behaviour you want is doable, but requires some basic understanding of mathematics, data binding, and event listening. What you basically want is to check, for each scroll event fired on the window object, that:
The shorter of the two elements, #leftcontent vs #rightcontent, has its bottom touching the bottom of the viewport
If yes, the shorter element will have a fix position so that it "sticks" to the bottom of the viewport
If no, the shorter element will be unfixed
The position fixing can be done by adding a .fixed class, which tells the browser to use position: fixed; bottom: 0; on the element. Now the interesting part: how do you know if the element has touched the bottom? The trick is simple:
You calculate the bottom of the viewport from the top. This is just the viewport height + the viewport scroll from the top of the document.
You calculate the bottom of the element from the top. This is the element's offset + the element's height.
When the value in point #1 is more than point #2, you know that you have scrolled beyond the bottom of the element. With that in mind, all you need is some logic to determine which of the two element is the shorter one: this is already covered in another StackOverflow question, so we can repurpose that logic for our use.
The last bit is that we only want to store the element's offset once: we use jQuery's .data() method to store the left + top offsets, and its height.
Here is a code that can be used:
function isBottom(el) {
// Cache element
var $el = $(el);
// Store element's offset and height once only
var elOffsetTop = $el.offset().top;
var elOffsetLeft = $el.offset().left;
var elHeight = $el.height();
if ($el.data('offsetTop') === void 0)
$el.data('offsetTop', elOffsetTop);
if ($el.data('offsetLeft') === void 0)
$el.data('offsetLeft', elOffsetLeft);
if ($el.data('height') === void 0)
$el.data('height', elHeight);
// Check if element is at bottom of viewport
var viewportBottom = $(window).height() + $(window).scrollTop();
var elementBottom = $el.data('offsetTop') + $el.data('height');
return viewportBottom > elementBottom;
}
$(window).on('scroll', function() {
// Get the shorter of the two elements
// From: https://stackoverflow.com/a/13319029/395910
var shortest = [].reduce.call($('#leftcontent, #rightcontent'), function(sml, cur) {
return $(sml).height() < $(cur).height() ? sml : cur;
});
// If element is bottom, add the class 'fixed'
$(shortest)
.toggleClass('fixed', isBottom(shortest))
.css('left', isBottom(shortest) ? $(shortest).data('offsetLeft') : 'auto');
});
Note that we need to assign a left property, because if the right column is the shorter one, we need to know how far it is originally offset from the left, so that position: fixed will position it correctly.
The .fixed class is dead simple:
#header {
// Other styles
// Add z-index so the fixed content does not overlay header
z-index: 1;
}
.fixed {
position: fixed;
bottom: 0;
z-index: 1;
}
Pro-tip: you might want to consider throttling/debouncing your scroll handler callback, for performance reasons.
See proof-of-concept example below:
for(var i=1000;i--;){
$('#rightcontent').append(i+ ' ');
$('#leftcontent').append("i ");
}
function isBottom(el) {
// Cache element
var $el = $(el);
// Store element's offset and height once only
var elOffsetTop = $el.offset().top;
var elOffsetLeft = $el.offset().left;
var elHeight = $el.height();
if ($el.data('offsetTop') === void 0)
$el.data('offsetTop', elOffsetTop);
if ($el.data('offsetLeft') === void 0)
$el.data('offsetLeft', elOffsetLeft);
if ($el.data('height') === void 0)
$el.data('height', elHeight);
// Check if element is at bottom of viewport
var viewportBottom = $(window).height() + $(window).scrollTop();
var elementBottom = $el.data('offsetTop') + $el.data('height');
return viewportBottom > elementBottom;
}
$(window).on('scroll', function() {
// Get the shorter of the two elements
// From: https://stackoverflow.com/a/13319029/395910
var shortest = [].reduce.call($('#leftcontent, #rightcontent'), function(sml, cur) {
return $(sml).height() < $(cur).height() ? sml : cur;
});
// If element is bottom, add the class 'fixed'
$(shortest)
.toggleClass('fixed', isBottom(shortest))
.css('left', isBottom(shortest) ? $(shortest).data('offsetLeft') : 'auto');
});
#font-face {
font-family: Gill Sans MT;
src: url("Gill Sans MT.ttf");
}
body{
margin-top: 0px;
margin-left: 0px;
margin-right: 0px;
margin-bottom: 0px;
}
#header{
position: fixed;
text-align: center;
font-size: 32px;
background: #f4f4e6;
padding-top: 2px;
min-width: 100%;
min-height: 28px;
z-index: 2;
}
#content{
padding-top: 38px;
text-align: center;
}
#footer{
position: fixed;
text-align: center;
background: #f4f4e6;
min-width: 100%;
font-style: italic;
font-family: Gill Sans MT;
letter-spacing: -1;
}
#rightcontent{
float: right;
max-width: 55%;
padding-bottom: 20px;
}
#leftcontent{
padding-bottom: 20px;
max-width: 45%;
}
.fixed {
position: fixed;
bottom: 0;
z-index: 1;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="header">
Worki do odkurzaczy
</div>
<script>
document.write("<div id=content width=" + document.documentElement.clientWidth + "px height=" + (document.documentElement.clientHeight - 64) + "px style=padding-bottom:20px;min-height:" + (document.documentElement.clientHeight - 52) + "px; data-scroll-offset=28>");
</script>
<div id="rightcontent" name="target">
</div>
<div id="leftcontent">
</div>
<script>
document.write("<div id=footer style=top:" + (document.documentElement.clientHeight - 22) + "px;>");
//IDK WHY ON LOCAL TEST IS HERE -32px
</script>
Related
I am using the following HTML/Javascipt code to make the classic percentage bar.
function update() {
var element = document.getElementById("myprogressBar");
var width = 1;
var identity = setInterval(scene, 10);
function scene() {
if (width >= 70) {
clearInterval(identity);
} else {
width++;
element.style.width = width + '%';
element.innerHTML = width * 1 + '%';
}
}
}
#Progress_Status {
width: 50%;
background-color: #ddd;
}
#myprogressBar {
width: 1%;
height: 35px;
background-color: #4CAF50;
text-align: center;
line-height: 32px;
color: black;
}
<!DOCTYPE html>
<html>
<body>
<h3>Example of Progress Bar Using JavaScript</h3>
<p>Download Status of a File:</p>
<div id="Progress_Status">
<div id="myprogressBar">1%</div>
</div>
<br>
<button onclick="update()">Start Download</button>
</body>
</html>
What I would like to obtain and I am trying to achieve with .innerHTML is the following situation
The vertical line has to appear at the same level of the specified percentage.
For the vertical bar I used an added div nested inside the #Progress_Status container. It's styled to be absolute positioned and to change its offset in % in sync with the progress bar width.
For it to work, its container was set to position:relative as the reference frame.
function update() {
//fetches the vertical bar elements
var vbar = document.querySelector("#Progress_Status .percverticalbar");
var element = document.getElementById("myprogressBar");
var width = 1;
var identity = setInterval(scene, 10);
function scene() {
if (width >= 70) {
clearInterval(identity);
} else {
width++;
//updates the left offset of the vertical bar
vbar.style.left = `${width}%`;
element.style.width = width + '%';
element.innerHTML = width * 1 + '%';
}
}
}
#Progress_Status {
width: 50%;
background-color: #ddd;
position: relative;
}
.percverticalbar{
position: absolute;
height: 100px;
width: 5px;
background: gray;
top: -25px;
left: 0;
}
#myprogressBar {
width: 1%;
height: 35px;
background-color: #4CAF50;
text-align: center;
line-height: 32px;
color: black;
margin: 50px 0;
}
<h3>Example of Progress Bar Using JavaScript</h3>
<p>Download Status of a File:</p>
<div id="Progress_Status">
<div id="myprogressBar">1%</div>
<div class="percverticalbar"></div>
</div>
<br>
<button onclick="update()">Start Download</button>
You could just add an :after pseudo element and add the following styles to it. Keep in mind that the parent, in the case #myprogressBar should be relatively positioned.
#myprogressBar {
width: 1%;
height: 35px;
background-color: #4CAF50;
text-align: center;
line-height: 32px;
color: black;
position: relative;
}
#myprogressBar:after {
width: 5px;
height: 80px;
background: #333;
content: '';
position: absolute;
right: -5px;
top: 50%;
transform: translateY(-50%);
border-radius: 5px;
}
I'm trying to resize rotated (transformed with css) object with vanilla js. Object origin is center and can't be changed.
I've found a function here which should do the thing, however still it doesn't looks ideal - left top corner change its position (half of pixels or so).
Here is a simplified codepen example.
What code modifications I need to do at getCorrection to keep left top corner position always same?
UPDATE:
As per comments below, the calculation is accurate but browser just can't deal with fractions of a pixel perfectly which seems like a technical limitation? Any ideas how to fix?
If you apply the transforms relative to the top left corner (transform-origin: top left), then you don't need to calculate that correction factor. You can then wrap all the shapes if you need to position them on the page:
const obj = document.querySelector('.object');
const btn = document.querySelector('button');
let h = 100;
let w = 100;
btn.addEventListener('click', () => {
h += 5;
w += 5;
updateElement();
});
function updateElement() {
obj.style.width = w + 'px';
obj.style.height = h + 'px';
}
updateElement();
body {
margin: 0;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
overflow-y: scroll;
overflow-x: hidden;
}
button {
margin: 8px;
padding: 8px;
font-family: monospace;
border: 2px solid black;
background: transparent;
}
.container {
position: relative;
width: 100%;
height: 100%;
}
.object-initial {
position: absolute;
top: 16px;
left: 30%;
outline: 1px dashed black;
transform: rotate(20deg);
transform-origin: top left;
width: 100px;
height: 100px;
}
.object {
position: absolute;
top: 16px;
left: 30%;
outline: 1px solid black;
transform: rotate(20deg);
transform-origin: top left;
}
<button>Increase width and height</button>
<div class="container">
<div class="object"></div>
<div class="object-initial"></div>
</div>
Finally I was able to resolve issue with sub-pixels rendering. I've changed an approach slightly - instead of updating left & top coordinates with offset, I've used css translate() to apply the same offset. Works very well.
Seems transform has much better sub-pixels rendering optimizations.
Here is a codepen example.
function updateElement() {
obj.style.left = l + 'px';
obj.style.top = t + 'px';
obj.style.width = w + 'px';
obj.style.height = h + 'px';
obj.style.transform = 'rotate(' + r + 'deg) translate(' + ox + 'px, ' + oy + 'px)';
}
Same formula might be reused with slight adjustments to preserve any of rectangle corners.
I'm in a blind spot with my small jQuery script.
The point is that I'm trying to make an element to rotate, and to apply the rotation value dynamically as the user is scrolling through the page.
It works here on stackoverflow but I can't get this to work on my website...
The only external library I'm using is JQuery.
Can you please tell me where is the problem?
var $animObject = $('.animateObject');
var $window = $(window);
$window.on('scroll', function() {
var fromTop = $window.scrollTop() / -4;
$animObject.css('transform', 'rotate(' + fromTop + 'deg)')
});
.header {
width: 100%;
height: 100vh;
background-image: url('https://simply-design.ml/dev/img/start1.jpg');
display: flex;
justify-content: center;
align-items: center;
}
.header-content {
padding: 30px;
max-width: 470px;
}
.header-wrapper {
padding: 50px;
border: solid 3px #fff;
}
.header h1 {
font-size: 30px;
color: #fff;
text-align: center;
}
.header p {
font-size: 20px;
color: #fff;
text-align: center;
}
.p-title {
font-size: 14px;
color: #fff;
}
.head-button {
padding: 10px 25px;
background-color: #3b88df;
color: #fff;
font-size: 20px;
cursor: pointer;
font-family: 'Source Sans Pro', sans-serif;
}
.head-button:hover {
background-color: #2c78ce;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<header class="header">
<div class="header-content">
<center>
<div class="header-wrapper animateObject">
<h1>title</h1>
<div style="height: 2px; width: 70px; background-color: #fff; margin: 20px;"></div>
<p>subtitle</p>
</div>
</center>
</div>
</header>
<div style="height: 1000px"></div>
Check this example I've made without jQuery, which shows how to rotate an element based on the scroll position of the window, but only once the element is in view.
I've decided to do this without jQuery because it's better for performance, working directly with the DOM instead of passing through jQuery, and also because it's relatively simple code, understandable.
Find out how much was scrolled
Get the target's element absolute position
Calculate if the element is within the viewport (if not, break)
If it's in, save the scroll value at that point
Subtract that value from the current scroll value to get the value from that point on
Use the new value as baseline for the transformation
var elm = document.querySelector('b');
var onScroll = (function(){
var startPos;
function run(){
var fromTop = window.pageYOffset,
rect = elm.getBoundingClientRect(),
scrollDelta;
// check if element is in viewport
if( (rect.top - window.innerHeight) <= 0 && rect.bottom > 0 )
startPos = startPos === undefined ? fromTop : startPos;
else{
startPos = 0;
return;
}
scrollDelta = (fromTop - startPos) * 1; // "speed" per scrolled frame
elm.style.transform = `translateX(${scrollDelta}px) rotate(${scrollDelta}deg)`;
console.clear();
console.log(scrollDelta);
}
run();
return run;
})()
window.addEventListener('scroll', onScroll);
html, body{ height:100%; }
body{ height:1500px; }
b{
position:fixed;
top: 20px;
left:20px;
width:100px;
height:100px;
background:red;
}
<b></b>
inspect the <b> element while scrolling and see that it only gets transform when it is in view.
I was trying to move the divs (here it's question number) based on the prev and next button. So that the selected question is always visible on screen.
Here is the demo : http://jsfiddle.net/arunslb123/trxe4n3u/12/
Screen :
click and question number and click prev or next button to understand my issue.
My code :
$("#next")
.click(function () {
$(".c.current-question")
.each(function () {
var divIdx = $(this)
.attr('id');
var scrollTo = $('#' + divIdx)
.position()
.left;
$("#scrollquestion")
.animate({
'scrollLeft': scrollTo
}, 800);
});
});
$("#prev")
.click(function () {
$(".c.current-question")
.each(function () {
var divIdx = $(this)
.attr('id');
var scrollTo = $('#' + divIdx)
.position()
.left;
$("#scrollquestion")
.animate({
'scrollLeft': -scrollTo
}, 800);
});
});
Using scrollLeft is a bit tricky. I did a small redo of your use-case based on positioning and then moving it based on left of the container. The tricky part is to reliably calculate the negative position when scrolled to the extreme right. Also, need to take into account the widths and margins.
Check the below snippet:
var $wrap = $("#numWrap"), $strip = $("#strip"),
$leftArrow = $(".wrapper > .arrows").first(),
wrapWidth = $wrap.width() + $leftArrow.width(),
margin = 10;
fill(20); select($(".numberItem").first());
$strip.on("click", ".numberItem", function() { select($(this)); });
function select($elem) {
$(".numberItem").removeClass("selected");
$elem.addClass("visited").addClass("selected");
focus($elem[0]);
}
function focus(elem) {
var stripPos = $strip.position(),
numPos = $(elem).offset(),
elemWidth = $(elem).width() + margin,
numRight = numPos.left + elemWidth;
if (numRight > wrapWidth) {
$strip.css({"left": stripPos.left - elemWidth});
}
if (numPos.left < (margin + $leftArrow.width())) {
$strip.css({"left": stripPos.left + elemWidth});
}
}
$(".wrapper").on("click", "a.arrow", function() {
var stripPos = $strip.position();
if (this.id == "lft") {
$strip.css({"left": stripPos.left + (wrapWidth / 2)});
} else {
$strip.css({"left": stripPos.left - (wrapWidth / 2)});
}
});
$(".controls").on("click", "a.arrow", function() {
var $sel = $(".selected"), numPos, $sel, elemWidth;
$elem = $sel.length > 0 ? $sel.first() : $(".numberItem").first();
if (this.id == "lft") {
$sel = $elem.prev().length > 0 ? $elem.prev() : $elem;
select($sel);
} else {
$sel = $elem.next().length > 0 ? $elem.next() : $elem;
select($sel);
}
numPos = $sel.offset(); elemWidth = $sel.width() + margin;
numRight = numPos.left + elemWidth;
if (numPos.left > wrapWidth) {
$strip.css({"left": -($sel.text()) * $sel.width() });
}
if (numRight < 0) {
$strip.css({"left": +($sel.text()) * $sel.width() });
}
});
function fill(num){
for (var i = 1; i <= num; i++) {
var $d = $("<a href='#' class='numberItem'>" + i + "</a>");
$strip.append($d);
}
}
* { box-sizing: border-box; padding: 0; margin: 0; font-family: sans-serif; }
div.wrapper {
background-color: #ddd; width: 100vw; height: 64px;
clear: both; overflow: hidden; margin-top: 16px;
}
div.arrows {
float: left; width: 10%; min-width: 24px; height: 64px; line-height: 64px;
text-align: center; vertical-align: middle; overflow: hidden;
}
div.numWrap {
float: left; height: 64px; line-height: 64px;
width: 80%; vertical-align: middle;
overflow: hidden; position: relative;
}
div.strip {
position: absolute; left: 0px;
width: auto; white-space: nowrap;
transition: left 1s;
}
a.numberItem {
display: inline-block; text-align: center; margin: 0px 8px;
background-color: #fff; border-radius: 50%; width: 48px; height: 48px;
font-size: 1.2em; line-height: 48px; text-decoration: none;
}
a.numberItem.visited { background-color: #fff; color: #000; border: 2px solid #01aebc; }
a.numberItem.selected { background-color: #01aebc; color: #fff; }
div.controls { clear: both; }
div.controls > div.arrows { width: auto; margin: 0 12px; }
a, a:focus, a:active, a:link, a:visited {
display: inline-block;
text-decoration: none; font-weight: 600;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="wrapper">
<div class="arrows">
<a id="lft" class="arrow" href="#">〈</a>
</div>
<div id="numWrap" class="numWrap">
<div id="strip" class="strip"></div>
</div>
<div class="arrows">
<a id="rgt" class="arrow" href="#">〉</a>
</div>
</div>
<div class="controls">
<div class="arrows">
<a id="lft" class="arrow" href="#">〈 Previous</a>
</div>
<div class="arrows">
<a id="rgt" class="arrow" href="#">Next 〉</a>
</div>
<div>
Explanation:
Using absolute positioning on the number container, which is nested to get 100% width.
Markup:
<div class="wrapper">
<div class="arrows"><a id="lft" class="arrow" href="#">〈</a></div>
<div id="numWrap" class="numWrap">
<div id="strip" class="strip"></div> <!-- nesting here -->
</div>
<div class="arrows"><a id="rgt" class="arrow" href="#">〉</a></div>
</div>
CSS:
div.wrapper {
background-color: #ddd; width: 100vw; height: 64px;
clear: both; overflow: hidden; margin-top: 16px;
}
div.arrows {
float: left; width: 10%; min-width: 24px; height: 64px; line-height: 64px;
text-align: center; vertical-align: middle; overflow: hidden;
}
div.numWrap {
float: left; height: 64px; line-height: 64px;
width: 80%; vertical-align: middle;
overflow: hidden; position: relative; /* relatively positioned */
}
div.strip {
position: absolute; left: 0px; /* absolutely positioned */
width: auto; white-space: nowrap;
transition: left 1s; /* instead of jquery animate */
}
With this structure, we can now use left to control the scrolling.
For partially obscured numbers, try to gently focus-in (nudge into view) a number which is partially obscured. This can be done by checking the position relative to parent and adding the width/margin to it and also accounting for width of the left arrow (it might peep thru).
Javascript:
function focus(elem) {
var stripPos = $strip.position(),
numPos = $(elem).offset(),
elemWidth = $(elem).width() + margin,
numRight = numPos.left + elemWidth;
// if it is towards right side, nudge it back inside
if (numRight > wrapWidth) {
$strip.css({"left": stripPos.left - elemWidth});
}
// if it is towards left side, nudge it back inside
if (numPos.left < (margin + $leftArrow.width())) {
$strip.css({"left": stripPos.left + elemWidth});
}
}
Once the user has scrolled the list too far and then tries to click on previous / next buttons to select a question, then we need to move the entire container upto the selected number. We can easily do this by multiplying the question number with element width and then changing the left in positive (if towards right) or in negative (if towards left).
Javascript:
// if left of element is more than the width of parent
if (numPos.left > wrapWidth) {
$strip.css({"left": -($sel.text()) * $sel.width() });
}
// if right of element is less than 0 i.e. starting position
if (numRight < 0) {
$strip.css({"left": +($sel.text()) * $sel.width() });
}
Here is a fiddle to play with: http://jsfiddle.net/abhitalks/aw166qhx/
You will need to further adapt it to your use-case, but you get the idea.
I'm doing a simple animation by click a box to move to the right by 100px on each click. And I want that when its position reaches 400, it should move to the left by 100px on each click.
I did a console.log() but the console only show the initial positioning of the element, and it doesn't update after each click.
I also want the final position to show on the <span id="info"> but it also doesn't update.
Why is that?
Many Thanks
jquery:
$('#somebox > a').click(function(e){
var thisElem = $(this),
aPos = thisElem.position();
thisElem.animate({ marginLeft: '+=100px' });
if(aPos.left === 400){
thisElem.animate({ marginLeft: '-=100px' });
}
console.log('finish pos: ' + aPos.left);
$('#info').text(aPos.left + 'px');
e.preventDefault();
});
HTML:
<div id="somebox">
show
<span id="info">10px</span>
</div>
CSS:
#somebox{
position: relative;
background: #EEF0EB;
border: 1px solid #dedbd7;
height: 300px;
width: 500px;
}
#somebox a{
display: block;
color: #fff;
font-weight: bold;
border: 1px solid #099;
background: #0C9;
padding: 5px;
width: 50px;
text-decoration: none;
}
#info{
display: block;
font-size: 36px;
color: #777;
font-weight: bold;
width: 100px;
margin: 100px auto;
}
How about using the .data() method to make your life easier?
$('#somebox > a').click(function(e){
var $this = $(this);
var rel = $this.data('rel') || '+=';
var step = 100, // amount to move each click
min = 0, // minimum left movement
max = 400; // maximum right movement
$this.animate({ marginLeft: rel + step + 'px' },function(){
var margin = parseInt($this.css('margin-left'),10);
if (margin <= min || margin >= max){
$this.data('rel',(rel == '+=' ? '-=' : '+='));
}
});
});
DEMO
Add this to your style sheet:
#somebox a {
position: relative;
}
And on your jQuery where it says marginLeft, change it to just left. Do some more tweaking afterwards to get it to show the proper value.