How to animate rotateX on scroll in pure JS? - javascript

I am trying to create a scroll effect with an image whose rotateX is something like 70deg (or any number).
Whenever someone scrolls the image into viewport, the rotateX of the image has to become 0deg.
In the same way, if someone scrolls the image out of the viewport, the rotateX of the image has to become 70deg again
Here is my code:
let a = 70
function test(){
let image = document.querySelector("img");
let imageTop = image.getBoundingClientRect().top;
let screenpos = window.innerHeight /2
// console.log("test")
if(imageTop < screenpos){
image.style.border = "5px solid green"
// console.log(window.scrollY/10)
image.style.transform = `rotateX(${a=a-2}deg)`
// console.log("its reached ")
}
}
window.addEventListener("scroll",function(){
test()
})
body {
background-color: #ccc;
text-align: center;
margin-top: 100px;
font-family: sans-serif;
}
.bgcolor {
background-color: black;
color: rgba(255, 255, 255, 0.8);
}
div{
perspective:800px;
margin-top:400px;
margin-bottom:200px;
}
div img {
transform:rotateX(66deg);
transition:.9s;
/* border: 1px solid #000; */
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>Scroll Please</h1>
<div><img src="https://cdn.pixabay.com/photo/2021/06/10/22/14/stork-6327150__340.jpg" alt="Bird Image"></div>
</body>
</html>

I have created you a small snippet using the example from the Mozilla docs of the Intersection Observer API.
To get a better understanding of everthing going on, feel free to check out the linked docs.
const image = document.querySelector("img");
// Create the Observer on page load
window.addEventListener("load", (event) => {
createObserver();
}, false);
// Setup the Observer
function createObserver() {
let observer;
let options = {
root: null,
rootMargin: "0px",
threshold: buildThresholdList()
};
observer = new IntersectionObserver(handleIntersect, options);
observer.observe(image);
}
// Getting an array with 1000 values between 0.0 and 1.0
function buildThresholdList() {
let thresholds = [];
let numSteps = 1000;
for (let i=1.0; i<=numSteps; i++) {
let ratio = i/numSteps;
thresholds.push(ratio);
}
thresholds.push(0);
return thresholds;
}
// What to do with the observer intersections
function handleIntersect(entries, observer) {
entries.forEach((entry) => {
// Only get values between 0 and 0.5, so that the image only
// ...starts animating when half visible
const maxxedIntersect = entry.intersectionRatio > 0.5
? entry.intersectionRatio - 0.5
: 0;
// Scale the number (0.0 ... 0.5) between 0 and 70
const scaled = scaleBetween(maxxedIntersect, 0, 70, 0, 0.5);
// Get the value that the thing should rotate
// When the element is fully visible, the scaled value will be 70,
// ... so we have to sub from 70 to get 0 in this example
const rotateValue = parseInt(70 - scaled);
// Apply the style
image.style.transform = `rotateX(${rotateValue}deg)`
});
}
// Helper function for scaling numbers between min and max
// Look here: https://stackoverflow.com/a/31687097/9150652
function scaleBetween(unscaledNum, minAllowed, maxAllowed, min, max) {
return (maxAllowed - minAllowed) * (unscaledNum - min) / (max - min) + minAllowed;
}
body {
background-color: #ccc;
text-align: center;
margin-top: 100px;
font-family: sans-serif;
}
.bgcolor {
background-color: black;
color: rgba(255, 255, 255, 0.8);
}
div > img{
margin-top: 400px;
margin-bottom: 600px;
perspective: 800px;
border: 5px solid green;
transition: .1s;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>Scroll Please</h1>
<div>
<img src="https://cdn.pixabay.com/photo/2021/06/10/22/14/stork-6327150__340.jpg"
alt="Bird Image">
</div>
</body>
</html>

Related

Why the transform translate is not working?

I am using this code to simply move a small element(ball here), but the transform translate is only working once, even when the setInterval() function is continuously working?
This is the html file --
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="MoveTheBall.css">
<title>Move the ball</title>
</head>
<body>
<div class="ball"></div>
<script src="MoveTheBall.js"></script>
</body>
</html>
and CSS file--
.ball{
height: 5rem;
width: 5rem;
background-color: salmon;
border-radius: 50%;
position: relative;
top: 35vw;
left: 45vw;
}
and the JavaScript file--
var ball = document.querySelector('.ball');
document.addEventListener('keypress' , function(event) {
console.log('key', event.key);
if(event.key === 'w') {
moveUp();
}
});
function moveUp() {
let ballRect;
let interval = setInterval(function() {
ballRect = ball.getBoundingClientRect();
if(ballRect.top > 0) {
ball.style.transform = 'translateY(-10px)';
} else {
clearInterval(interval);
}
}, 300);
}
Here I was expecting to move the ball up until it reaches to the top of the screen after pressing w key. But the ball is only moving only once.
function moveUp() {
let ballRect;
let interval = setInterval(function() {
ballRect = ball.getBoundingClientRect();
if(ballRect.top > 0) {
let currentTop = parseFloat(getComputedStyle(ball).getPropertyValue('top'));
ball.style.transform = `translateY(${currentTop - 10}px)`;
} else {
clearInterval(interval);
}
}, 300);
}

(JS/CANVAS/HTML) - isPoinToPath inaccurate?

Below here, the provided snippet for testing purpose.
The yellow(y) represent the canvas area, return "n" on click
The red(r) represent the click area, return "y" on click
The trouble(x) represent the error, return "y" when clicked
How to make it right?
yyyyyyyyyyyyyyy
yyrrrrrrrrrrryy
yyrrrrrrrrrrryy
yyxxxxxxxxxxxyy
yyyyyyyyyyyyyyy
const canvas = document.getElementById('test');
const context = canvas.getContext('2d');
testClicker();
function testClicker() {
const buttonz = new Path2D();
buttonz.rect(canvas.width / 2 - 125, canvas.height / 2 - 70, 250, 80);
context.fillStyle = 'red';
context.fill(buttonz);
var mouseEvent = (event) => {
// Check whether point is inside rect
const isPointInPath = context.isPointInPath(buttonz, event.offsetX, event.offsetY);
let a = isPointInPath ? 'y' : 'n';
alert(a);
}
canvas.addEventListener("click", mouseEvent, false);
}
* {
margin: 0px;
padding: 0px;
}
#test {
position: fixed;
top: 1%;
left: calc(50% - 150px);
width: 300px;
height: 100px;
background: yellow;
}
<!DOCTYPE html>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<link rel="stylesheet" href="style.css">
<html>
<body>
<canvas id="test"></canvas>
</body>
<script src="main.js"></script>
</html>
You have a mismatch between the CSS setting of height of canvas (100px) and the canvas's attribute height (set to 150px by default).
You are using canvas.height as the basis for setting the path drawing on the canvas.
This snippet makes things match up by setting the height attribute of the canvas to 100.
const canvas = document.getElementById('test');
const context = canvas.getContext('2d');
testClicker();
function testClicker() {
const buttonz = new Path2D();
alert(canvas.height);
buttonz.rect(canvas.width / 2 - 125, canvas.height / 2 - 70, 250, 80);
context.fillStyle = 'red';
context.fill(buttonz);
var mouseEvent = (event) => {
// Check whether point is inside rect
alert(event.offsetY);
const isPointInPath = context.isPointInPath(buttonz, event.offsetX, event.offsetY);
let a = isPointInPath ? 'y' : 'n';
alert(a);
}
canvas.addEventListener("click", mouseEvent, false);
}
* {
margin: 0px;
padding: 0px;
}
#test {
position: fixed;
top: 1%;
left: calc(50% - 150px);
width: 300px;
height: 100px;
background: yellow;
}
<!DOCTYPE html>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<link rel="stylesheet" href="style.css">
<html>
<body>
<canvas id="test" width="300" height="100"></canvas>
</body>
<script src="main.js"></script>
</html>
See MDN for further explanation of the difference between setting dimensions of the canvas using the width/height attributes and using CSS. If they differ you get scaling/distortion.

Continuous call on EventListener on mousedown

I'm working on an Etch A Sketch app for The Odin Project and I want to adjust one of the requirements where all I need to do is move the mouse on screen as a continuous stroke where each pixel touched on mouseover is filled, which is what's happening right now with my code.
What I want to change is the mouseover on my tile EventListener where it's really a continuous stroke on mousedown. I did try changing the e.target command in the setTiles function to toggle after changing the tile.addEventListener to mousedown, but it only works on each press of the left mouse button.
I'm trying to figure out a way to make this continuous on mousedown instead of using mouseover. I've included the code I have so far in the question.
const container = document.querySelector('#container');
const grid = document.querySelector('#grid');
const userInput = document.querySelector('#user-input');
let penDown = false;
grid.style.fontSize = '1em';
// Set pixel width and height
let wdt = '1em';
let hgt = '1em';
// Ask the user for the number of tiles for the sketch grid
function getUserInput() {
let input = parseInt(prompt(`Please enter the grid size you'd like`));
input <= 100 || !isNaN(input)
? createSketchGrid(input)
: alert('Please enter a valid number less than or equal to 100.');
}
// Event listener to create tiles in mouseover
function setTiles(e) {
e.target.classList.add('fill');
}
// function deleteTiles(e) {
// e.target.classList.toggle('fill');
// }
container.addEventListener('mousedown', function () {
penDown = true;
});
container.addEventListener('mouseup', function () {
penDown = false;
});
// Create the grid
function createSketchGrid(tiles) {
let gridSize = tiles * tiles;
for (let i = 0; i < gridSize; i++) {
let tile = document.createElement('div');
tile.style.width = wdt;
tile.style.height = hgt;
grid.appendChild(tile);
// tile.addEventListener('mouseover', setTiles, false);
if ((penDown === true)) {
tile.addEventListener('mousemove', setTiles);
}
}
}
userInput.addEventListener('click', getUserInput);
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
font-size: 10px;
}
h1 {
font-size: 3rem;
}
.grid {
/* font-size: 1.6rem; */
display: flex;
flex-wrap: wrap;
gap: 0.1em;
border: 2px solid black;
background-color: lightgrey;
flex: 0 0 32em;
}
.fill {
flex-wrap: wrap;
background-color: black;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Etch A Sketch</title>
<link rel="stylesheet" type="text/css" href="styles/style.css">
<script type="text/javascript" src="js/main.js" defer></script>
</head>
<body>
<h1 class="title">Etch A Sketch</h1>
<div id="container" class="container">
<button id="user-input" class="btn user-input" value="Grid Size">Grid Size</button>
<div id="grid" class="grid">
</div>
</div>
</body>
</html>

Why my loading text not stand out tho it has bigger z-index than my background image?

I'm making a blurry loading effect. My background image has a z-index of -1 and my loading text has no z-index. Everything works except my loading text, I can't see it loading and stand out from my background image...
Does anybody know what's wrong? it'd be kind of you to help! Thank you so much!
This is my JS:
const loadText = document.querySelector('.loading-text')
const bg = document.querySelector('.bg')
let load = 0
let int = setInterval(blurring, 30)
const scale = (num, in_min, in_max, out_min, out_max) => {
return ((num - in_min) * (out_max - out_min)) / (in_max - in_min) + out_min
}
function blurring() {
load++
if (load > 99) {
clearInterval(int)
}
loadText.innerText = `${load}%`
loadText.style.opacity = scale(load, 0, 100, 1, 0)
bg.style.filter = `blur(${scale(load, 0, 100, 30, 0)}px)`
}
This is my html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Blurry Loading</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<section class="bg">
<div class="loading-text">
0%
</div>
</section>
<script src="script.js"></script>
</body>
</html>
This is my CSS:
* {
box-sizing: border-box;
}
body {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
overflow: hidden;
margin: 0;
}
.bg {
background: url('z.jpg') no-repeat center center/cover;
position: absolute;
top: -30px;
left: -30px;
width: calc(100vw + 60px);
height: calc(100vw + 60px);
z-index: -1;
filter: blur(0px)
}
.loading-text {
padding: 50% 50%;
font-size: 50px;
color: #fff;
It's actually visible, but very poorly because you're putting blur filter on bg element and everything inside (that includes your loading-text). Just put your loading-text outside.
This problem is occurring because loading-text is inside bg. So when the blur effect is applied to bg it automatically gets applied to loading-text as well. So the loading percentage is being rendered, but it's blurred into the background image and therefore not visible.
One solution is to have two elements within your bg element. These elements would not be nested and therefore any style applied to either element would not affect the other element. The first element would show the background image and the second element would show the loading text.
Here is a way to do that using your code, but I've removed anything that isn't necessary to achieve this effect:
const loadText = document.querySelector('.loading-text')
const bg = document.querySelector('.bg-image')
let load = 0
let int = setInterval(blurring, 30)
const scale = (num, in_min, in_max, out_min, out_max) => {
return ((num - in_min) * (out_max - out_min)) / (in_max - in_min) + out_min
}
function blurring() {
load++
if (load > 99) {
clearInterval(int)
}
loadText.innerText = `${load}%`
loadText.style.opacity = scale(load, 0, 100, 1, 0)
bg.style.filter = `blur(${scale(load, 0, 100, 30, 0)}px)`
}
.bg,
.bg-image {
position: absolute;
top: 0;
left: 0;
width: 200px;
height: 190px;
}
.bg-image {
background-image: url('https://upload.wikimedia.org/wikipedia/commons/thumb/e/e1/FullMoon2010.jpg/200px-FullMoon2010.jpg');
filter: blur(30px);
}
.loading-text {
color: #fff;
font: normal 32px serif;
text-align: center;
line-height: 190px;
}
<!DOCTYPE html>
<html lang="en">
<body>
<section class="bg">
<div class="bg-image">
</div>
<div class="loading-text">
0%
</div>
</section>
</body>
</html>

How to implement resizable div with fixed increments in Javascript?

I want to implement a vertical resizable div such that it increases height in certain fixed intervals. I don't want to use any libraries and looking for plain JS implementation. Here is jquery version: https://jqueryui.com/resizable/#snap-to-grid
This code works good but increases height by one pixel on each mousemove (vertically).
When I try to increase height by 10, the height increases but the mouse pointer does not stick to the base of div..,
https://plnkr.co/edit/3VesixHE2B7N46f8N7Bg?p=preview
const divElement = document.querySelector('.ns-resize');
const element = document.querySelector('.resizable');
let original_height = parseFloat(getComputedStyle(element, null).getPropertyValue('height').replace('px', ''));
let original_Y = element.getBoundingClientRect().top;
divElement.onmousedown = function(mouseDownEvent) {
mouseDownEvent.preventDefault();
var original_mouse_y = mouseDownEvent.pageY;
document.addEventListener('mousemove', resize);
document.addEventListener('mouseup', stopResize);
function resize(mouseUpEvent) {
//cmp.increment = cmp.increment + 15;
const height = original_height + (mouseUpEvent.pageY - original_mouse_y);
if (height > 15 && height < 900) {
mouseUpEvent.pageY = mouseUpEvent.pageY ;
element.style.height = height + 'px';
divElement.innerHTML = element.style.height;
}
}
function stopResize() {
document.removeEventListener('mousemove', resize)
}
}
/* Styles go here */
.resizable {
width: 100px;
height : 100px;
border:1px solid red;
position: absolute;
}
.container {
position : relative;
}
.ns-resize {
position: absolute;
bottom: 0px;
cursor: ns-resize;
width: 100%;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="resizable">
<div class="ns-resize">
<span> </span>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
}
I could increase height by one pixel on every mousemove event, but what I really want is to increase by 10px each time.
As indicated in my comment, CSS already has a resize property you can use to make an element resizable. The only thing you need to do after a resize, is round the current height to the nearest 10px.
document.querySelector( '.resizable' ).addEventListener( 'mouseup', event => {
const current_height = parseInt( event.target.style.height, 10 );
event.target.style.height = Math.round( current_height / 10 ) * 10 + 'px';
console.log( `changed height from ${ current_height }px to ${ event.target.style.height }` );
});
.resizable {
border: 1px solid red;
height: 100px;
overflow: hidden;
resize: vertical;
width: 100px;
}
<div class="resizable"></div>
Only mighty want to add a check now to not run the function when something else inside the div is clicked.

Categories

Resources