How can I get the right position of the scrollbar-thumbnail? - javascript

I'm trying to detect the right position of a scrollbar-thumb. Can somebody please explain if this is possible. The scrollbar thumbnail does not have fixed width. I'm using nw.js, ES6 and jQuery library.
The following is a simplified extraction of my code.
class C {
constructor() {
win.on('resize', this._resize.bind(this));
$('#divId').on('scroll', this._scroll.bind(this));
}
_getXScrollbarThumbPos() {
let lpos = $('#divId').scrollLeft();
let rpos = lpos + SCROLLBAR_THUMBNAIL_WIDTH; // FIXME how can I get the scrollbarThumbnailWidth
return rpos;
}
_resize() {
let x = this._getXScrollbarThumbPos();
//..
}
_scroll() {
let x = this._getXScrollbarThumbPos();
//..
}
}
The resize and scroll listener work ok, the only bottleneck is how to determine the width of the scrollbar-thumbnail. win is the nw.js wrapper of the DOM's window, see here (initialization is not shown here, as it is not relevant for the question).

This is the solution I've eventually used. While I haven't found a way to directly obtain any dimensions of the right scrollbar-thumb position due to the dynamic width (for which I've also have not detected a way to calculate this), I have actually been able to solve the problem; By determining the width of the total content (i.e. the viewable + overflowing content), and subtracting both the viewable content width and scrolled to the left content-width, we can determine uttermost right position of the scrollable content, within the viewport, which (neglecting any possible border widths) equals the right position of the scollbar-thumb.
class C {
constructor() {
win.on('resize', this._resize.bind(this));
$('#divId').on('scroll', this._scroll.bind(this));
}
_getXScrollbarThumbPos() {
// width of the content + overflow
let totalWidth = $('#divId').scrollWidth;
// width of the visible (non overflowing) content
let viewWidth = $('#divId').width();
// the amount of pixels that the content has been scrolled to the left
let lpx = document.getElementById('#divId').scrollLeft;
// the amount of pixels that are hidden to right area of the view
let rpx = totalWidth - viewWidth - lpx;
// represents the right position of the scrollbar-thumb
console.log(rpx);
return rpx;
}
_resize() {
let x = this._getXScrollbarThumbPos();
//..
}
_scroll() {
let x = this._getXScrollbarThumbPos();
//..
}
}

Related

Create a wiggle effect for a text

So what I want to happen is that when viewing the Span the text is normal but as you scroll down it starts moving until it looks like such:
Before the effect:
While the effect occurs:
The header is represented by spans for each letter. In the initial state, the top pixel value for each is 0. But the idea as mentioned is that that changes alongside the scroll value.
I wanted to keep track of the scroll position through JS and jQuery and then change the pixel value as needed. But that's what I have been having trouble with. Also making it smooth has been another issue.
Use the mathematical functions sine and cosine, for characters at even and odd indices respectively, as the graphs of the functions move up and down like waves. This will create a smooth effect:
cos(x) == 1 - sin(x), so in a sense, each character will be the "opposite" of the next one to create that scattered look:
function makeContainerWiggleOnScroll(container, speed = 0.01, distance = 4) {
let wiggle = function() {
// y-axis scroll value
var y = window.pageYOffset || document.body.scrollTop;
// make div pseudo-(position:fixed), because setting the position to fixed makes the letters overlap
container.style.marginTop = y + 'px';
for (var i = 0; i < container.children.length; i++) {
var span = container.children[i];
// margin-top = { amplitude of the sine/cosine function (to make it always positive) } + { the sine/cosine function (to make it move up and down }
// cos(x) = 1 - sin(x)
var trigFunc = i % 2 ? Math.cos : Math.sin;
span.style.marginTop = distance + distance * trigFunc(speed * y)/2 + 'px';
}
};
window.addEventListener('scroll', wiggle);
wiggle(); // init
}
makeContainerWiggleOnScroll(document.querySelector('h2'));
body {
height: 500px;
margin-top: 0;
}
span {
display: inline-block;
vertical-align: top;
}
<h2>
<span>H</span><span>e</span><span>a</span><span>d</span><span>e</span><span>r</span>
</h2>
Important styling note: the spans' display must be set to inline-block, so that margin-top works.
Something like this will be the core of your JS functionality:
window.addEventListener('scroll', function(e) {
var scrl = window.scrollY
// Changing the position of elements that we want to go up
document.querySelectorAll('.up').forEach(function(el){
el.style.top = - scrl/30 +'px';
});
// Changing the position of elements that we want to go down
document.querySelectorAll('.down').forEach(function(el){
el.style.top = scrl/30 +'px';
});
});
We're basically listening in on the scroll event, checking how much has the user scrolled and then act upon it by offsetting our spans (which i've classed as up & down)
JSBin Example
Something you can improve on yourself would be making sure that the letters wont go off the page when the user scrolls a lot.
You can do this with simple math calculation, taking in consideration the window's total height and using the current scrollY as a multiplier.
- As RokoC has pointed out there is room for performance improvements.Implement some debouncing or other kinds of limiters

'Media-queries' for javascript - different code for different viewport height/widths

The problem
I'm using javascript to calculate widths of elements to achieve the layout I'm after. The problem is, I don't want to load the code on smaller screen sizes (when the screen width is less than 480px for example). I'd like this to work on load and on browser/viewport resize.
I'd consider small screen devices 'the default' and working up from there. So, none of the following script is called by default, then if the browser width is greater than 480px (for example), the following script would be called:
The code
$(document).ready(function() {
//Get the figures width
var figure_width = $(".project-index figure").css("width").replace("px", "");
//Get num figures
var num_figures = $(".project-index figure").length;
//Work out how manay figures per row
var num_row_figures = Math.ceil(num_figures / 2);
//Get the total width
var row_width = figure_width * num_row_figures;
//Set container width to half the total
$(".project-index").width(row_width);
x = null;
y = null;
$(".project-index div").mousedown(function(e) {
x = e.clientX;
y = e.clientY;
});
$(".project-index div").mouseup(function(e) {
if (x == e.clientX && y == e.clientY) {
//alert($(this).next().attr("href"));
window.location.assign($(this).next().attr("href"));
}
x = y = null;
});
});
// Drag-on content
jQuery(document).ready(function() {
jQuery('#main').dragOn();
});
The extra bit
The slight difference on larger screens is to do with the browser/viewport height. This is in regards to the line:
var num_row_figures = Math.ceil(num_figures / 2);
You can see once the calculation has a value, it divides it by 2. I only want this to happen when the browser/viewport height is above a certain amount - say 600px.
I'd be happy with this being the 1st state and then the value is divided by 2 if the height is greater than 600px if it's easier.
Can anyone help me/shed some light on how to manage my script this way. I know there's media queries for managing CSS but I can't seem to find any resources for how to manage javascript this way - hope someone can help.
Cheers,
Steve
You can use window.matchMedia, which is the javascript equivalent of media queries. The matchMedia call creates a mediaQueryList object. We can query the mediaQueryList object matches property to get the state, and attach an event handler using mediaQueryList.addListener to track changes.
I've added an example on fiddle of using matchMedia on load and on resize. Change the bottom left pane height and width (using the borders), and see the states of the two queries.
This is the code I've used:
<div>Min width 400: <span id="minWidth400"></span></div>
<div>Min height 600: <span id="minHeight600"></span></div>
var matchMinWidth400 = window.matchMedia("(min-width: 400px)"); // create a MediaQueryList
var matchMinHeight600 = window.matchMedia("(min-height: 600px)"); // create a MediaQueryList
var minWidth400Status = document.getElementById('minWidth400');
var minHeight600Status = document.getElementById('minHeight600');
function updateMinWidth400(state) {
minWidth400Status.innerText = state;
}
function updateMinHeight600(state) {
minHeight600Status.innerText = state;
}
updateMinWidth400(matchMinWidth400.matches); // check match on load
updateMinHeight600(matchMinHeight600.matches); // check match on load
matchMinWidth400.addListener(function(MediaQueryListEvent) { // check match on resize
updateMinWidth400(MediaQueryListEvent.matches);
});
matchMinHeight600.addListener(function(MediaQueryListEvent) { // check match on resize
updateMinHeight600(MediaQueryListEvent.matches);
});
#media screen and (max-width: 300px) {
body {
background-color: lightblue;
}
}
So i searched a bit and came up with this example from w3 schools .http://www.w3schools.com/cssref/tryit.asp?filename=trycss3_media_example1
i think this is something you are trying to achieve.
For pure js , you can get the screen width by screen.width

Jump to next div and have it centred on screen when user scrolls

I'm building a one page site, and wanting to have multiple divs, that are approximatly 400px (but vary) on top of each other. Instead of a smooth scroll, I would like jump to the next div and have it centred on the screen, at the same time adjust the opacity of the content above and below to draw attention to the centre div.
I have tried playing with a few scrolling plugins but have not had anything do what I'm after, most of them are geared towards a full page div, not one only a 1/3 or so the height of the page.
Can someone point me towards something I can adapt to perform this.
Add an event listener to the document which looks for elements with the class .next
Get the distance from the top of the viewport to the top of the element in question
subtract half the value of the viewport height minus the height of the element.
If the element is bigger than the viewport, scoll to to top of the element
Or scroll the element into the middle.
Set the opacity of the unfocused elements.
(Demo)
(function(){
"use strict";
document.addEventListener('click', function(e) {
if(e.target.className.indexOf('next') >= 0) {
var current = e.target.parentElement
var next = current.nextElementSibling || false;
if(next) {
var nextNext = next.nextElementSibling;
var height = next.offsetHeight;
var top = next.offsetTop;
var viewHeight = window.innerHeight;
if(viewHeight - height > 0) {
var scrollTo = top - ((viewHeight - height) / 2);
} else {
var scrollTo = top;
}
window.scroll(0, scrollTo);
current.style.opacity = '0.5';
next.style.opacity = '1';
if(nextNext) nextNext.style.opacity = '0.5';
}
}
}, false);
})();

javascript (but not jQuery please) fixed position on x-axis but not y?

I've looked everywhere and so far have not found a non-jQuery js to handle this. I would like to avoid using a library for just this one simple task.
I would like to fix three navigation divs ("#header", "#tabs" and "#footer") to viewport left (or alternatively, to the x position of a div "#helper" with "position: fixed; left: 0; top: 0;") -- but not fix y. They can not be vertically fixed.
I've created a working js that forces the divs to reposition based on scrolling, but it's not smooth in the real page (too many dynamic and graphic elements) - I'd like it to either animate smoothly, or mimic fixed-left and not appear to reposition at all.
Anyone who can give pointers or a quick script, or review and modify the script I have made? I've noticed people tend to ask why an obvious solution is not used instead of answering the question... I will be glad to answer, but would prefer help with the actual problem.
Here is a jsFiddle with the problem: http://jsfiddle.net/BMZvt/6/
Thank you for any help!
Smooth animation example:
var box = document.getElementById('box');
var moveTo = function(obj, target) {
// start position
// you should obtain it from obj.style
var cpos = {
x: 0,
y: 0
}
var iv = setInterval(function(){
cpos.x += (target.x - cpos.x) * 0.3; // 0.3 is speed
cpos.y += (target.y - cpos.y) * 0.3; // 0.3 is speed
obj.style.left = Math.floor(cpos.x) + 'px';
obj.style.top = Math.floor(cpos.y) + 'px';
var dist = Math.abs(cpos.y - target.y); // distance (x+y) from destination
dist += Math.abs(cpos.x - target.x); // < 1 = object reached the destination
if(dist < 1) { // here we are checking is box get to the destination
clearInterval(iv);
}
}, 30); // this is also the speed
}
box.onclick = function(){
moveTo(box, {x: 90, y: 75}); // fire this function to move box to specified point
}
Demonstration: http://jsfiddle.net/Qwqf6/5/
Your script is your job, but this is a quick start how to solve animation problem
You can also do some fancy stuff with speed for example use sin(x) to set the speed
Demonstration #2 http://jsfiddle.net/Qwqf6/6/ (very smooth)
Full script here https://gist.github.com/3419179
I don't think there's a straight way to do this...
But here's a way.
First, You need to be able to detect the direction of the scrolling when window.onscroll event happens. You would do this by comparing the current page offsets with the newly acquired page offsets whenever the scroll event happens. (http://stackoverflow.com/questions/1222915/can-one-use-window-onscroll-method-to-include-detection-of-scroll-direction)
Now suppose you know the direction of the scroll, you want to change the styling for the divs depending on the direction of the scroll.
Let FixAtX be the value of the x coordinate that you want to fix your divs at.
Let OriginalY be the y coordinate of the divs.
Also whenever scrolling happens, despite of the direction, you want to remember the pageoffset X and Y. Let's call them OldX and OldY
If scrolling vertically:
Set position value for divs' style to be absolute.
Set top value for divs' style to be OriginalY
Set left value for divs' style to be OldX + FixAtX
If scrolling horizontally:
Set position value for divs' style to be fixed.
set top value for divs' style to be OriginalY - OldY (<- this may be different depending on how the browser computes pageOffset value,)
Set Left value for divs' style to be FixAtX
I think this should work...
Since you are just using browser's rendering for positioning, it should be very smooth!
hope I understood the question correctly.
This is for people who view this post - I wound up going with the solution I initially put together in the jsFiddle that used a simple javascript to mimic fixed x.
The javascript in the first answer was hefty and wound up buggy, and the second answer sounded good but did not work in practice. So, I'm recommending the javascript from the jsFiddle (below) as the best answer to fixed x and fluid y without a javascript library. It's not perfect and has a minimal delay but is the best answer I've found.
function fixLeft() {
function getScrollX() {
var x = 0, y = 0;
if( typeof( window.pageYOffset ) == 'number' ) {
x = window.pageXOffset;
} else if( document.body && ( document.body.scrollLeft) ) {
x = document.body.scrollLeft;
} else if( document.documentElement && ( document.documentElement.scrollLeft) ) {
x = document.documentElement.scrollLeft;
}
return [x];
}
var x = getScrollX();
var x = x[0];
// have to get and add horizontal scroll position px
document.getElementById('header').style.left = x + "px";
document.getElementById('tabs').style.left = x + "px";
document.getElementById('footer').style.left = x + "px";
}
window.onscroll = fixLeft;

HTML/Javascript: Fix scroll position after screen rotation

What is the best way to restore the scroll position in an HTML document after the screen has been rotated? (This is in a Cocoa Touch UIWebView, but I think it's a problem everywhere.) The default behavior seems to restore the y-offset in pixels, but since the text has been reflowed this is now a different position in the document.
My initial thought is to:
Pepper the document with invisible, uniquely-id'ed elements.
Before rotation, search for the element e whose y-offset is closest to the scroll offset.
After rotation, update the scroll offset to e's new y-offset.
Even if that works, I'd prefer not to insert a bunch of crud into the document. Is there a better way?
Here's a diagram to clarify the problem. Restoring the original y-offset does not produce the intended result because more text fits on a line in landscape mode.
Not pretty but it works. This requires there to be span tags throughout the document text.
// Return the locator ID closest to this height in pixels.
function findClosestLocator(height) {
var allSpans = document.getElementsByTagName("span");
var closestIdx = 0;
var closestDistance = 999999;
for(var i = 0; i < allSpans.length; i++) {
var span = allSpans[i];
var distance = Math.abs(span.offsetTop - height);
if(distance < closestDistance) {
closestIdx = i;
closestDistance = distance;
}
}
return allSpans[closestIdx].id;
}
After rotation, document.getElementById(spanId).offsetTop is the new y-offset, where spanId is the result of findClosestLocator() before rotation.
Conceptually the problem isn't so hard to think about. You have scroll events, rotation events, and variables. I would track the scrollTop position on the document.body DOM node on the scroll event. Reapply it with the orientation event fires.
Something like this perhaps.
// Track position
var pos;
// On scroll update position
document.body.addEventListener("scroll", function() {
pos = document.body.scrollTop;
}, true);
// On rotation apply the scroll position
window.addEventListener("orientationchange", function() {
document.body.scrollTop = pos;
}, true);

Categories