I'm following a tutorial online, and this one has left me stumped after a few attempts and research.
Basically when a user scrolls down the page an image should appear when it peaks at about 50% of it's height from the bottom.
However none of my images are appearing? In the code when I loop through each image to find it's height from the bottom, and console.log it shows me it's position no problem.
const slideInAt = (window.scrollY + window.innerHeight);
When I attempt to divide the height of the image in half console.log shows me NaN.
const slideInAt = (window.scrollY + window.innerHeight) -
sliderImage.height / 2;
I'm not sure if that's preventing the images from being shown or not? Here is the full section of code I'm referring to.
const sliderImages = document.querySelectorAll('.animation-element');
function checkSlide(e) {
sliderImages.forEach(sliderImage => {
// half way through the image
const slideInAt = (window.scrollY + window.innerHeight) -
sliderImage.height / 2;
// bottom of the image
const imageBottom = sliderImage.offsetTop + sliderImage.height;
const isHalfShown = slideInAt > sliderImage.offsetTop;
const isNotScrolledPast = window.scrollY < imageBottom;
if (isHalfShown && isNotScrolledPast) {
sliderImage.classList.add('in-view');
} else {
sliderImage.classList.remove('in-view');
}
});
}
window.addEventListener('scroll', debounce(checkSlide));
The loop is also always showing false when I'm scrolling up and down in devTools.
My apologies if I'm not explaining it correctly, still learning. Here is a simplified version of my code in JSFiddle
Thanks in advance!
You are trying to get the height of a div, not an image:
function checkSlide(e) {
sliderImages.forEach(sliderImage => {
// get the height of the image, not the div
const height = sliderImage.querySelector("img").height;
const slideInAt = (window.scrollY + window.innerHeight) -
height / 2;
debugger;
// bottom of the image
const imageBottom = sliderImage.offsetTop + height;
const isHalfShown = slideInAt > sliderImage.offsetTop;
const isNotScrolledPast = window.scrollY < imageBottom;
if (isHalfShown && isNotScrolledPast) {
sliderImage.classList.add('in-view');
} else {
sliderImage.classList.remove('in-view');
}
});
}
https://jsfiddle.net/5eog6z42/2/
Related
I'm making a slider from scratch and I'm having an issue with the absolute positioning which has to be relative to the viewport (whenever the window height changes). Basically the slider has a bar and a button (to slide) and I wrote code to get the relative button position to the bar:
const getRelativeCoordinates = (event, referenceElement) => {
const position = {
x: event.pageX,
y: event.pageY
};
const offset = {
left: referenceElement.offsetLeft,
top: referenceElement.offsetTop
};
return {
x: position.x - offset.left,
y: position.y - offset.top,
};
}
and I wrote code to know after a cursor slide where the button would change his position and what percentage 1-100 actually is:
const handleSlider = (e) => {
const sliderBar = document.getElementById('slider-bar')
const sliderBtn = document.getElementById('slider-button')
const sliderHeight = parseInt(sliderBar.getBoundingClientRect().height) - sliderBarOffsetY
const cursorY = getRelativeCoordinates(e, document.getElementById('slider-bar')).y
let slidingPerc = ((sliderHeight - cursorY) / sliderHeight) * 100
if (slidingPerc > 100) slidingPerc = 100
else if (slidingPerc < 0) slidingPerc = 0
let barPerc = sliderBarMaxY - ((slidingPerc * (sliderBarMaxY - sliderBarMinY)) / 100)
return {
barPerc,
slidingPerc
}
}
The problem is that sliderBarMaxY and sliderBarMinY (83 and 9) are constants of max and min values of the button absolute top position in percentage (83% and 9%). These values changing the viewport height would not work anymore and if I try to change them by window.innerHeight (83 : 460 = x : innerHeight) gives an offset-error (in my case of 17)
I am attempting to adapt this JS solution to keep a floating element above the footer of my site.
The adaption I am attempting is instead of changing the element position to absolute, I would have a dynamic bottom px value based on the position of the top of the footer, relevant to the client window.
function checkOffset() {
var onlineFloat = document.querySelector('#online-ceo');
var footer = document.querySelector('.site-footer');
function getRectTop(el){
var rect = el.getBoundingClientRect();
return rect.top;
}
if((getRectTop(onlineFloat) + document.body.scrollTop) + onlineFloat.offsetHeight >= (getRectTop(footer) + document.body.scrollTop) - 20)
var newBottom = ((getRectTop(footer) + document.body.scrollTop) - 40).toString().concat('px');
onlineFloat.style.bottom = newBottom;
if(document.body.scrollTop + window.innerHeight < (getRectTop(footer) + document.body.scrollTop))
onlineFloat.style.bottom = '20px';// restore when you scroll up
}
document.addEventListener("scroll", function(){
checkOffset();
});
The output of newBottom is currently a px value which changes on scroll, however, I am having issues setting this position to the element.
Where am I going wrong? Thanks.
With your approach (changing the bottom property), you can just calculate where the "float" should be if the footer's top position is in view (as in window.innerHeight) on scroll.
function checkOffset() {
var onlineFloat = document.querySelector('#online-ceo');
var footer = document.querySelector('.site-footer');
function getRectTop(el) {
var rect = el.getBoundingClientRect();
return rect.top;
}
var newBottom = 10 + (getRectTop(footer) < window.innerHeight ? window.innerHeight - getRectTop(footer) : 0) + 'px';
onlineFloat.style.bottom = newBottom;
}
document.addEventListener("scroll", function () {
checkOffset();
});
So I tried to block the draggable contents from overflowing the body.
It works on top and on the left side.
I can't limit the right side and bottom.
e.pageX -= e.offsetX;
e.pageY -= e.offsetY;
// left/right constraint
if (e.pageX - dragoffset.x < 0) {
offsetX = 0;
} else if (e.pageX + dragoffset.x > document.body.clientWidth) {
offsetX = document.body.clientWidth - e.target.clientWidth;
} else {
offsetX = e.pageX - dragoffset.x;
}
// top/bottom constraint
if (e.pageY - dragoffset.y < 0) {
offsetY = 0;
} else if (e.pageY + dragoffset.y > document.body.clientHeight) {
offsetY = document.body.clientHeight - e.target.clientHeight;
} else {
offsetY = e.pageY - dragoffset.y;
}
el.style.top = offsetY + "px";
el.style.left = offsetX + "px";
}
});
};
Also, my divs are getting glitchy while I drag them around. They only stop on the right side and bottom when the text inside them is selected.
There is drag&drop library with feature to restrict movements of draggables
https://github.com/dragee/dragee
By default it restrict movements of element inside container. It take into account size of element.
import { Draggable } from 'dragee'
new Draggable(element, { container: document.body })
In same time it's possible to use custom bound function
import { Draggable, BoundToRectangle, Rectangle } from 'dragee'
new Draggable(element, {
bound: BoundToRectangle.bounding(Rectangle.fromElement(document.body, document.body))
})
where BoundToElement describe restrictions that you need
class BoundToRectangle {
constructor(rectangle) {
this.rectangle = rectangle
}
bound(point, size) {
const calcPoint = point.clone()
const rectP2 = this.rectangle.getP3()
if (this.rectangle.position.x > calcPoint.x) {
(calcPoint.x = this.rectangle.position.x)
}
if (this.rectangle.position.y > calcPoint.y) {
calcPoint.y = this.rectangle.position.y
}
if (rectP2.x < calcPoint.x + size.x) {
calcPoint.x = rectP2.x - size.x
}
if (rectP2.y < calcPoint.y + size.y) {
calcPoint.y = rectP2.y - size.y
}
return calcPoint
}
}
Size of element you can calculate next way:
let width = parseInt(window.getComputedStyle(element)['width'])
let height = parseInt(window.getComputedStyle(element)['height'])
I can also suggest to use translate3d property for movements instead of positions left and top, because it is more efficient
I assume that since the draggable item is a dom element it will push the width and height of the body when moved right or down, this changes the body size, so using the body size might not be the right way. Maybe you can use window.width and window.height or so to restrict the area with values the draggable item cannot change. Hope this helps.
I have an Angular application that loads multiple images on to the page at random locations by utilizing the following code:
HTML:
<div ng-repeat="s in selectedImages" class="container">
<img ng-src="{{s.img}}" class="sImage" ng-style="s.pos"/>
</div>
CSS:
.wordImage {
position:absolute;
width:100px;
}
JS Controller:
function loadImages() {
$scope.myImages = ['img1.png', 'img2.png', 'img3.png', 'img4.png', 'img5.png']
$scope.selectedImages = [];
for (i in $scope.myImages) {
$scope.selectedImages.push(addRandomLocationToImage($scope.myImages[i]));
}
}
function addRandomLocationToImage(image) {
image.pos = {};
var preTop = getRandomHeight(); // get Height for this image
var preLeft = getRandomWidth(); // get Width for this image
image.pos = {top:preTop,
left:preLeft};
return image; // returns the same image object but contains pos{} for ng-style
}
function getRandomHeight() {
var imgHeight = 100;
var winHeight = $(window).height();
var randomH = Math.random() * (winHeight - 100); // subtract 100 for the header. and also footer padding
if (randomH < 150) {
randomH += 150; // add to keep it out of the header
}
if(winHeight - randomH < 100) { // if image will be on bottom edge of page
randomW -= imgHeight; // subtract 100 because that is the height of the images, this will prevent them from being partially off the page
}
return randomH;
}
function getRandomWidth() {
var imgWidth = 100;
var winWidth = $(window).width();
var randomW = Math.random() * winWidth;
if (randomW < 0) { // make sure it is not less than zero, then assign new number until it is greater than zero
while (randomW < 0) {
randomW = Math.random() * winWidth;
}
}
if (winWidth - randomW < 100) { // if image will be on right edge of page
randomW -= imgWidth; // subtract 100 because that is the width of the images, this will prevent them from being partially off the page
}
return randomW;
}
loadImages();
This definitely generates random images on a page...but they overlap very easily. My question is, how can I prevent them from overlapping? Here is some code that I have been working on.
var newLeft = currentImage.pos.left;
var newTop = currentImage.pos.top;
for (i in $scope.selectedImages) {
var originalLeft = $scope.selectedImages[i].pos.left;
var originalTop = $scope.selectedImages[i].pos.top;
if ((originalLeft - newLeft < 100 && originalLeft - newLeft > -100) && // could overlap horizontally
(originalTop - newTop < 100 && originalTop - newTop > -100)) { // could overlap vertically
//do something to select a new random location.
}
}
Basically you have to check a image position with every other image. You can use Array.some for this:
Considering that you store the image position in the image object as
x and y properties
function checkCollision(testImage) {
return $scope.myImages.some(function(img) {
if (img == testImage)
return false;
if (!img.x || !img.y) // Image has no position yet
return false;
return
testImage.x < img.x + imgWidth &&
testImage.x + imgWidth > img.x &&
testImage.y < img.y + imgHeight &&
testImage.y + imgHeight > img.y;
});
}
You have to be aware that depending of the available space and image sizes there might be a situation where is not possible to find a suitable position for a image.
I made a functional example.
I'm calling an API that returns a URL to an image, the image could be any size and it's completely random.
I'm trying to resize images to fit within the page, ensuring the content is not pushed below the fold, or that the image doesn't hit the width of the page.
I've written some Javascript below, I've been testing it and am getting some strange results - the console logs are saying that the image is one size, but the element selector in Chrome's dev tools is usually saying something completely different. I'm sure I've made some basic mistake in my code, if you could take a look that would be great.
Javascript sets viewport height and width, checks if a photo src is available. Once the image has loaded, it checks if the natural dimensions are greater than that of the viewport, if so it attempts to resize - this is where the script is failing.
//check viewport
var viewportWidth = getWidth();
var viewportHeight = getHeight();
//get the media
if (data[2] == "photo") {
var tweetImage = document.getElementById("tweetImage");
//when it loads check the size against the browser size
tweetImage.onload = function () {
console.log('image height: ' + tweetImage.naturalHeight);
console.log('viewport height: ' + viewportHeight);
//does it matter if its landscape?
if (viewportWidth - tweetImage.naturalWidth < 1) {
tweetImage.width = Math.floor(tweetImage.naturalWidth - (viewportWidth - tweetImage.naturalWidth) * 1.2);
console.log('w');
} else if (Math.floor(viewportHeight - tweetImage.naturalHeight) < 1) {
console.log('h');
console.log(viewportHeight - tweetImage.naturalHeight);
console.log('changed result: ' + Math.floor(tweetImage.naturalHeight - (Math.abs(viewportHeight - tweetImage.naturalHeight))));
tweetImage.height = Math.floor(tweetImage.naturalHeight - (Math.abs(viewportHeight - tweetImage.naturalHeight)*1.2));
} else {
tweetImage.height = Math.floor(viewportHeight / 2);
}
tweetImage.align = "center";
tweetImage.paddingBottom = "10px";
};
//tweetImage.height = Math.floor(viewportHeight / 2);
tweetImage.src = data[3];
}
One option would be to use a CSS-based solution like viewport height units.
.example {
height: 50vh; // 50% of viewport height
}
See http://web-design-weekly.com/2014/11/18/viewport-units-vw-vh-vmin-vmax/