I'm using the function in this JSFiddle to fetch the percentage of an element seen within the viewport on scroll (capped at 100 once user scrolls beyond it):
// Track percentage of section seen within viewport
// Assign target section to var
const element = document.getElementById('section--example');
// Calculate percentage of section in viewport
const percentageSeen = () => {
// Get the relevant measurements and positions
const viewportHeight = window.innerHeight;
const scrollTop = window.scrollY;
const elementOffsetTop = element.offsetTop;
const elementHeight = element.offsetHeight;
// Calculate percentage of the element that's been seen
const distance = (scrollTop + viewportHeight) - elementOffsetTop;
const percentage = Math.round(distance / ((viewportHeight + elementHeight) / 100));
// Restrict range between 0 — 100
return Math.min(100, Math.max(0, percentage));
}
Then, I'm attempting to use that percentageSeen value in the following function to animate an SVG path into view on scroll:
// Get a reference to the <path>
var path = document.querySelector('#path--example');
// Get length of SVG path
var pathLength = path.getTotalLength();
// Create dashes to match the length of the SVG path
path.style.strokeDasharray = pathLength + ' ' + pathLength;
// Initially offset dashes to hide SVG entirely
path.style.strokeDashoffset = pathLength;
path.getBoundingClientRect();
// Animate SVG on section scroll
window.addEventListener('scroll', function(e) {
// Fetch percentage seen
var scrollPercentage = percentageSeen;
// Length to offset the dashes
var drawLength = pathLength * scrollPercentage;
// Draw in reverse
path.style.strokeDashoffset = pathLength - drawLength;
});
I've created a JSFiddle here with my progress — I can log the visible percentage to the console without issue, but that value doesn't seem to be accepted when used in the above scroll function. I can also reveal the SVG based on page height (commented out in the JSFiddle), but not the section height. Can anyone help point me in the right direction that'll allow the use of the percentageSeen var in the scroll function?
The main problem is that percentageSeen is a percentage. When you multiply pathLength by percentageSeen you get a value that is 100 times larger than the value you want.
Also, you were calling the function without parentheses, which meant that scrollPercentage did not store the result, but the function itself.
I think you want the element to appear as the user scrolls. In that case, it is simpler to set strokeDasharray to 0 pathLength and then drawLength (pathLength-drawLenght).
You are capping percentageSeen at 100, which means that the last capping procedure is not necessary (also, you set the cap at 0.99 and you should have set it to 99).
I have forked the fiddle with a working example. As you can see, percentageSeen is never lower than 38 or larger than 61. You may want to fix the calculation.
// Animation — Stitch [Scroll]
// Reference: https://css-tricks.com/scroll-drawing/
// --------------------------------------------------
// Track percentage of animation container (section) in viewport
// Assign target section to var
const element = document.getElementById('section--example');
// Log percentage to console
const logPercentageSeen = () => {
//console.log(percentageSeen());
}
// Re-run 'logPercentageSeen' on scroll
window.addEventListener('scroll', logPercentageSeen);
// Calculate percentage of section in viewport
const percentageSeen = () => {
// Get the relevant measurements and positions
const viewportHeight = window.innerHeight;
const scrollTop = window.scrollY;
const elementOffsetTop = element.offsetTop;
const elementHeight = element.offsetHeight;
// Calculate percentage of the element that's been seen
const distance = (scrollTop + viewportHeight) - elementOffsetTop;
const percentage = Math.round(distance / ((viewportHeight + elementHeight) / 100));
// Restrict the range to between 0 and 100
return Math.min(100, Math.max(0, percentage));
}
// Log the initial value before any scrolling has happened
logPercentageSeen();
// Get a reference to the <path>
var path = document.querySelector('#path--example');
// Get length of path
var pathLength = path.getTotalLength();
// Make very long dashes (the length of the path itself)
path.style.strokeDasharray = '0 ' + pathLength;
// Offset the dashes so the it appears hidden entirely
path.style.strokeDashoffset = 0;
// Jake Archibald says so
// https://jakearchibald.com/2013/animated-line-drawing-svg/
path.getBoundingClientRect();
// When the page scrolls...
window.addEventListener('scroll', function(e) {
// What % down is it?
var scrollPercentage = percentageSeen();
// This runs, but is tied to the page height, NOT the section height
// var scrollPercentage = (document.documentElement.scrollTop + document.body.scrollTop) / (document.documentElement.scrollHeight - document.documentElement.clientHeight);
// Length to offset the dashes
var drawLength = pathLength * scrollPercentage / 100;
// Draw in reverse
var rest = pathLength - drawLength;
path.style.strokeDasharray = drawLength + ' ' + rest;
// When complete, remove the dash array, otherwise shape isn't quite sharp
// Accounts for fuzzy math
});
<section id="section--example">
<h1>Section heading</h1>
<p>It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English.</p>
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="300" height="300"
viewBox="0 0 900 512" style="enable-background:new 0 0 900 512;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;stroke:#000000;stroke-width:1.24;stroke-miterlimit:10;}
</style>
<path id="path--example" class="st0" d="M30.5,58.3c0,0,60.4-124,372,4.3c109.4,45,230.4,82.8,350.2,76.8c33.5-1.7,76.9-4.2,101.8-28.6
c25.6-25.1,17.3-60.8-11-79.5c-63.6-42.1-221,5.4-354.8,74.4c-40.3,21.1-81.4,40.7-122.9,59.4c-57.5,25.9-116,53.7-179.2,62.3
C153.5,232,67.6,238,61.5,189.2c-5.9-47,80-58.5,111.4-54.4c41,5.4,112.1,14,282.5,100.3c58.8,29.8,123.4,52.2,189.8,55.2
c29.2,1.3,91.5,2.8,104.1-31.5c8.1-22.1-9.3-42.5-30-46.9c-23.5-5-49.5,1.5-72.1,8c-45,12.9-88.3,32.2-130.6,52.3
c-26.8,12.8-53.4,26.2-79.5,40.5c0,0-87.3,49.6-140.2,49.6c-17.4,0-39.6-1.3-53.5-13.5c-13.1-11.6-13-30.9,2-41.3
c36.8-25.8,86,3.2,119.9,20.7c25.1,13,49.8,26.9,73.9,41.7c0,0,45.3,28,96,28c13.6,0,30.7-2.8,32-19.8c1.1-15.6-16.7-25.9-30-28
c-22.5-3.5-43.6,8.8-58,25.2c-41,46.4-18.3,114.3,25.9,133.7"/>
</svg>
<p>It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English.</p>
</section>
Related
I found the following code used to track the scroll percentage as you scroll down the page.
document.addEventListener('scroll', function(){
var h = document.documentElement,
b = document.body,
st = 'scrollTop',
sh = 'scrollHeight';
var percent = (h[st]||b[st]) / ((h[sh]||b[sh]) - h.clientHeight) * 100;
document.getElementById('scroll').textContent = 'scrolled: ' + percent + '%';
});
That shows the percentage of the whole page. If I only want the scroll percentage of a specific div like this example https://www.thefarmersdog.com/ how the dog bowl scrolls over when you move down the page.
You need to use getBoundingClientRect()
const coordinates = document.getElementById('your-id').getBoundingClientRect();
console.log(coordinates.top); // This is the distance to the top of the screen
Here is a detailed article that you can read if you need more info.
Divide number of pixels that an element's content has been scrolled vertically by the height of an element's content minus an element's offset height and multiply by 100 to get percentage value from 0 to 100. Optionally, round it to get an integer value.
scrollPercent = scrollTop / (scrollHeight - offsetHeight)
const scrollLabelElement = document.getElementById('scroll');
element.addEventListener('scroll', (_event) =>
const percentScrolled = Math.round(element.scrollTop / (element.scrollHeight - element.offsetHeight));
scrollLabelElement.textContent = `Scrolled: ${percentScrolled}%`;
});
I want to scale down logo in the header as user scrolling down the page.
When he hits a certain breakpoint, scaling should stop.
Also, scale can't be more or less specified hardcoded sizes.
Live case:
The header is separated into two section - top and bottom. The bottom part is sticky (pos: fixed) and the top part gets faded out (pos: absolute).
Logo should be smoothly scaled, so it's big (140px height) when all header elements are visible and small (50px height) when it's sticky
So, I can't figure out how to calculate a number within a range of limit[0] and limit[1] that is dependant on the percentage of scroll distance between 0 and 100 (100 is a header top height in my case)
Currently, it's scaling 1 to 0, but I need to scale 1 to 0.357
===
Short video (10s) to demonstrate a problem
https://monosnap.com/file/p5P7GMkn3BKKMu9glrnmBiO5X6951z
Codepen
https://codepen.io/dpmango/pen/EOXVww
===
$(window).on('scroll', function(e) {
var vScroll = _window.scrollTop();
var $logo = $('[js-header-logo]');
var $top = $('[js-header-top]');
var topHeight = $top.outerHeight();
var logoLimits = [1, 0.357] // [140, 50] // scale factor
var scrollPercent = 1 - (vScroll / topHeight) // 1 -> 0
var calcedScale = scrollPercent // what is logic ?
// limit rules
if ( vScroll > topHeight ){
calcedScale = logoLimits[1]
}
if ( vScroll < 0 ){
calcedScale = logoLimits[0]
}
// set values to DOM
$logo.css({
"transform": 'scale('+ calcedScale +')'
})
});
In the site that I am currently building I have an SVG path which is basically a straight line. When the user scrolls down the page, this path draws down the page until the user hits the bottom of the page.
The code I am using for this is taken from this page https://www.w3schools.com/howto/howto_js_scrolldrawing.asp
and can be seen here:
<svg style="min-height: 113.5%;" version="1.1" id="line_2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="4px" height="113.5%" xml:space="preserve">
<path style="max-height: 113.5%;" id="blue_line" fill="freeze" stroke-width="4" stroke="#3b7fdc" d="M0 0 v2884 400" />
</svg>
<script>
// Get the id of the <path> element and the length of <path>
var triangle = document.getElementById("blue_line");
var length = triangle.getTotalLength();
// The start position of the drawing
triangle.style.strokeDasharray = length;
// Hide the triangle by offsetting dash. Remove this line to show the triangle before scroll draw
triangle.style.strokeDashoffset = length;
// Find scroll percentage on scroll (using cross-browser properties), and offset dash same amount as percentage scrolled
window.addEventListener("scroll", myFunction);
function myFunction() {
var scrollpercent = (document.body.scrollTop + document.documentElement.scrollTop) / (document.documentElement.scrollHeight - document.documentElement.clientHeight);
var draw = length * scrollpercent;
// Reverse the drawing (when scrolling upwards)
triangle.style.strokeDashoffset = length - draw;
}
This works really well, however, currently, when the user scrolls back up the page, the SVG path 'reverses' back up the page with it. My desired effect is for the svg to only draw down the page and to not reverse when the user scrolls back up the page.
Does anyone know how to edit the script above to achieve this desired behaviour?
I've made a slight change to the script and it now does what you need...
<script>
// Get the id of the <path> element and the length of <path>
var triangle = document.getElementById("triangle");
var length = triangle.getTotalLength();
// The start position of the drawing
triangle.style.strokeDasharray = length;
// Hide the triangle by offsetting dash. Remove this line to show the triangle before scroll draw
triangle.style.strokeDashoffset = length;
// Find scroll percentage on scroll (using cross-browser properties), and offset dash same amount as percentage scrolled
window.addEventListener("scroll", myFunction);
var lastScrollpercent = 0;
function myFunction() {
var scrollpercent = (document.body.scrollTop + document.documentElement.scrollTop) / (document.documentElement.scrollHeight - document.documentElement.clientHeight);
if (scrollpercent < lastScrollpercent) return;
lastScrollpercent = scrollpercent;
var draw = length * scrollpercent;
// Reverse the drawing (when scrolling upwards)
triangle.style.strokeDashoffset = length - draw;
}
</script>
To summarise, I added lastScrollpercent and set it to 0. When the page is scrolled it works out the current percentage that it's been scrolled and only draws anything if that is currently more than lastScrollpercent, which it then updates (it's the value of scrollpercent last time anything was drawn). If the current scroll percent is less then it just returns (does nothing). That way, if you scroll up then it stops drawing, and if you then scroll down again it will only continue from where it left off.
I'm trying to create a chess board, and place it in the middle of the screen, so far i cannot get it to be directly in the center. i don't want to hard code the position to the screen because i'm going to be dealing with different screen sizes.
var winsize = cc.director.getWinSize();
var centerpos = cc.p(winsize.width / 2, winsize.height / 2);
for (i=0; i<64; i++){
var tile = cc.Sprite.create(res.tile_png);
this.addChild(tile,0);
tile.setPosition(winsize.width+i%8*50/-10, winsize.height-Math.floor(i/8)*50);
}
But the tiles and positioning is completely off
#jumpman8947, if you're using Cocos2d js perhaps you have a similar line: cc.view.setDesignResolutionSize(480, 320, cc.ResolutionPolicy.SHOW_ALL);
In this particular case the game will scale to any sceeen, but still run in 480x320 resolution, so no matter what screen resoultion you use, the center in the cocos world would always be cc.p(240, 160) so no matter what's the window size or the screen resolution, the resolution of the game stays the same
You can read more about resolution policies here (and in official js-doc):
http://www.cocos2d-x.org/wiki/Multiple_Resolution_Policy_for_Cocos2d-JS
Also please be aware, that the Sprite position in Cocos is the position of the centre of the sprite, not bottom left corner
In your question it's not completely clear exactly what you want. However, I made some assumptions. The explanation for my solution is embedded in the comments in the code below.
// var winsize = cc.director.getWinSize();
// Here is some example hard-coded return values:
var winsize = {width: 600, height: 400};
// You can change these numbers to see how they influence
// the outcome.
// var centerpos = cc.p(winsize.width / 2, winsize.height / 2);
// This line doesn't seem relevant for the question you asked.
// Or, rather, the following calculations will result in the tiles
// being centred on the screen anyway, so this calculation here
// is unnecessary.
// Being a chess board, I assume that you want the tiles to be square,
// i.e. to have the same width and height.
// If so, first find out which is the minimum dimension
// and calculate the tile size as being 1/8 of that.
var minDimn = Math.min(winsize.width, winsize.height);
var tileSize = minDimn / 8;
// Find out how far in from the left and how far down from the top
// you need the upper left corner of the upper left tile to start.
// This assumes that you don't need any "margin" around the board.
// (If you do need such a "margin", basically subtract it twice
// from each of winsize.width and winsize.height above.)
// Start with default values of 0 for each, but then add in the
// excess for the longer dimension, but divide it by two
// because that excess will be split between either
// the top and bottom or the left and right.
var xStart = 0, yStart = 0;
if (winsize.width > winsize.height) {
xStart = (winsize.width - winsize.height) / 2;
} else if (winsize.height > winsize.width) {
yStart = (winsize.height - winsize.width) / 2;
}
// Instead of looping through all 64 positions in one loop,
// loop through all the horizontal positions in an outer loop
// and all the vertical positions in an inner loop.
for (i = 0; i < 8; i++) {
// For the horizontal dimension, calculate x for each tile
// as the starting position of the left-most tile plus
// the width of the tile multiplied by the number of tiles (0-based)
var x = xStart + i * tileSize;
// Now the inner loop
for (j = 0; j < 8; j++) {
// Same type of calculation for the y value.
var y = yStart + j * tileSize;
// You can see the values in this demo here.
document.write("<pre>(" + x + ", " + y + ")</pre>");
// The following two lines don't seem to be relevant to the question.
// var tile = cc.Sprite.create(res.tile_png);
// this.addChild(tile,0);
// Use your calculated values in your function call.
// tile.setPosition(x, y);
}
}
I want to fill the window size with divs. For a specified div size in px, the screen will be filled as much as it can be, leaving a remainder edge amount of px on the side and bottom. This remainder amount is then divided by the number of cells in the row (or column) and that is then added to the height (or width) of each cell in the row (or column).
For the width this works perfectly but when the same logic is applied to the height, it breaks. Both width and height work in firefox.
Screenshot: http://i.imgur.com/mpDCM0G.png
JSfiddle of making the divs: https://jsfiddle.net/xb82c4zt/
Live: http://conwaygameoflife.heroku.com/
var windowWidth = window.innerWidth;
var windowHeight = window.innerHeight;
var size = 100;
// Calculate the number of cells we can fit in the width
//and height (there will be extra space)
w = Math.floor(windowWidth / size);
h = Math.floor(windowHeight / size);
// Calculate the extra space
var widthDiff = windowWidth % size;
var heightDiff = windowHeight % size;
// Add the needed amount of height and width to each cell to fill the window
var widthSize = size + widthDiff / w;
var heightSize = size + heightDiff / h;
// Begin to alter the DOM
var parentDiv = document.createElement('div');
parentDiv.className = 'grid';
for(var y = 0; y < h; y++) {
for(var x = 0; x < w; x++) {
var cellDiv = document.createElement('div')
cellDiv.className = 'cellDiv'
cellDiv.style.height = heightSize + 'px';
cellDiv.style.width = widthSize + 'px';
parentDiv.appendChild(cellDiv)
}
}
document.body.appendChild(parentDiv)
In Chrome (and probably other browsers), height and width pixel values are truncated! See this stackoverflow answer with the related jsFiddle
Precentage values are truncated too, but not as severely. So, to solve this you can convert pixels to percentages as I did in this jsFiddle.
The main thing I added was:
var widthPercent = widthSize / windowWidth * 100;
var heightPercent = heightSize / windowHeight * 100;
Because we're using percentages now, the parent container must have width/height:
parentDiv.style.height = windowHeight + 'px';
parentDiv.style.width = windowWidth + 'px';
And changed the loop to:
for(var x = 0; x < w*h; x++) {
var cellDiv = document.createElement('div');
cellDiv.className = 'cellDiv';
cellDiv.style.height = heightPercent + '%';
cellDiv.style.width = widthPercent + '%';
parentDiv.appendChild(cellDiv)
}
Now this doesn't always work in chrome perfectly. However, it does make it perfect in some cases... basically depends on when (and how drastic) the truncation of percentages is.
After further reflection, it looks like percentages get resolved to fractional pixel values as well... which still get truncated in Chrome. So, let's make our math better, and figure out the biggest non-fractional pixel value we can use... it's actually really easy. See here
Basically, we just floor the values, then center the grid so that we can make it look nice.
edit: wasn't very happy with this answer, so screwed with it some more. Added a function that found the closest multiple of window size and made it so that it would prefer that number. Makes it work in most screen sizes, and has a fallback to the percentage method if it doesn't perfectly work. See here. However, because it relies on a recursive (naive) algorithm to find the closest multiple, it's really easy to screw your browser performance. Limiting to only 5-10 pixels of search space helps. The gist of it:
function closestMultiple(width, size, n, limit) {
if(n > limit) {
return {m: width/size, s:size};
}
if((width % (size+n)) == 0) {
return {m: width / (size+n), s: size+n};
} else if((width % (size-n)) == 0) {
return {m: width / (size-n), s: size-n};
}
return closestMultiple(width, size, n+1, limit);
}
It's very naive and ignores things like "an odd width will never be divisible by an even number"... so there's a ton of room for improvement. Check out this discussion and this discussion for more on this.