CSS how to make an effect of a hand holding cards - javascript

So basically i want to have an transparent image of a hand holding cards, then i want to display cards on a curve like when you are holding 10 cards in your hand (not fixed 10 of course). So they should be positioned on a curved dome.
I'm working in angular, and i know that i have to accomplish this via position absolute and transform: translate-rotate css, just don't know how
I'm also working with bootstrap so this image with cards should be in a col-12 and compatible on a smaller screens.
I only have the parts of the code that i took from a guy that positioned elements in a circle - Bootstrap 3 align elements into circle
i tried to play around with translate and rotate but couldn't get it working

Here's the way I approached it. There's alot going on here, but it's basically
setting a overall width to work with
using a predefined number of cards and overall angle allowance
using math to distribute and angle the cards
using transform-origin: bottom center; to give the effect
let cards = document.querySelector('.cards');
let w = cards.offsetWidth;
let totalarc = 270;
let numcards = 7;
let angles = Array(numcards).fill('').map((a, i) => (totalarc / numcards * (i + 1)) - (totalarc/2 + (totalarc / numcards) / 2));
let margins = angles.map((a, i) => w / numcards * (i + 1));
angles.forEach((a, i) => {
let s = `transform:rotate(${angles[i]}deg);margin-left:${margins[i]}px;`
let c = `<div class='card' style='${s}'></div>`;
cards.innerHTML += c;
})
.container {
position: relative;
margin-left: 80px;
width: 100%;
}
.cards {
width: 150px;
}
.card {
width: 120px;
height: 200px;
background: #999;
border: 1px solid #000;
position: absolute;
opacity: .5;
transform-origin: bottom center;
}
<div class='container'>
<div class='cards'></div>
<div class='hand'>
<div>
</div>

Related

How can I make divs dynamically fill up fixed-size container and shrink and/or expand depending on number of divs in the container?

Introduction
I am currently learning web development and I am trying to make some sort of etch-a-sketch thing and it should pop out a grid with up to 100 square cells per side BUT no matter what number of cells per side the grid itself should be using the same amount of space, so for example if the total space it could use was 1000px then the grid, no matter how many cells it has, will use 1000px, no more or no less. My problem is that I'm not sure what I should do to make the grid do this.
Problem context For starters, for my purposes I can only generate the grid itself via Javascript. In the Javascript I use a doubly-nested for loop where after each iteration in the first loop, a new "grid-row" div will be created and then the second for loop will populate that grid row with "square" divs, so if I set the number as 5 per-side, it will create a grid-row and populate that row with 5 square divs 5 times.
My problem The whole grid itself is wrapped in a "grid-container" div with a fixed size. I would like to make it so the grid will always fill up the size of the grid container but not exceed the container itself. In other words, I would like to find a way to make the square and/or grid-row divs be able to shrink or grow depending on the number of them inside the grid-container so as to not have the overall grid exceed the size of the grid-container itself but still fill up the available space in the container.
So far I have played around with various CSS properties suggestions that I found from Google searches such as setting height and width of either the "square" or "grid-row" divs (or both of them at the same time) to 100%, playing around with the max widths and heights, as well as their mins, and playing around with the "overflow" and object-fit properties. I'm not sure if there was a solution for my problem in Javascript and if there was I haven't found one in my myriad Google searches.
Here's my code snippet, I'd appreciate any help:
const container = document.querySelector(".grid-container");
for (let x = 0; x < 6; x++) {
/* Both the "x" and "y" variables are set to arbitrary values (up to 100). The "x" variable here represents how many total "rows" the grid will create. The following "y" variable represents how many square divs will populate each row, so both of these numbers should be the same to make a square grid.*/
let newRow = document.createElement('div');
newRow.classList = `grid-row`;
container.appendChild(newRow);
for (let y = 0; y < 6; y++) {
let square = document.createElement('div');
square.classList = `square`;
newRow.appendChild(square);
}
}
const gridCells = document.querySelectorAll(".square");
gridCells.forEach(cell => {
cell.addEventListener("mouseover", () => {
cell.classList.add("hov-square");
});
});
/*This is the current state of the CSS file that I left on prior to posting this question. I set the width and height of the "square" divs to 10px each to make the grid visible and to give an idea of what the grid may look like, but my goal is for the grid (the grid-rows and the square divs inside of it) to be able to fill up the "grid-container" div that wraps the grid itself and resize itself depending on how many square cells are in the grid but never exceed the size of the grid-container itself.*/
body {
display: flex;
justify-content: center;
align-items: center;
margin: 0;
padding: 0;
background-color: black;
}
.grid-container {
margin-top: 10%;
display: flex;
width: 750px;
height: 750px;
justify-content: center;
align-items: center;
overflow: visible;
}
.grid-row {
max-width: 100%;
max-height: 100%;
}
.square {
width: 10px;
height: 10px;
border: 1px dashed white;
}
.hov-square {
background-color: grey;
}
<!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">
<script src="script.js" defer></script>
<link rel="stylesheet" href="styles.css">
<title>Etch-a-Sketch</title>
</head>
<body>
<div class="grid-container"></div>
</body>
</html>
I grabbed the width of the grid-container, then divided that by the number of columns to get the width/height of each cell.
Then I used javascript to set the width and height to that size.
I got rid of the grid-row CSS. Also, you don't need to loop through the cells to add hover class. Just use .square:hover in CSS and it will automatically apply it to the matching cells.
const container = document.querySelector(".grid-container");
let squares = 100;
let _parent_width = getComputedStyle(container).width;
let sq_size = Math.floor(_parent_width.replace("px","") / squares) - 2;
for (let x = 0; x < squares; x++) {
let newRow = document.createElement('div');
newRow.classList = `grid-row`;
container.appendChild(newRow);
for (let y = 0; y < squares; y++) {
let square = document.createElement('div');
square.classList.add("square");
square.style.width = sq_size + "px";
square.style.height = sq_size + "px";
newRow.appendChild(square);
}
}
const gridCells = document.querySelectorAll(".square");
gridCells.forEach(cell => {
cell.addEventListener("mouseover", () => {
cell.classList.add("hov-square");
});
});
/*This is the current state of the CSS file that I left on prior to posting this question. I set the width and height of the "square" divs to 10px each to make the grid visible and to give an idea of what the grid may look like, but my goal is for the grid (the grid-rows and the square divs inside of it) to be able to fill up the "grid-container" div that wraps the grid itself and resize itself depending on how many square cells are in the grid but never exceed the size of the grid-container itself.*/
body {
display: flex;
justify-content: center;
align-items: center;
margin: 0;
padding: 0;
background-color: black;
}
.grid-container {
display: flex;
width: 1000px;
height: 1000px;
justify-content: center;
align-items: center;
overflow: visible;
}
.square {
border: 1px dashed white;
}
.hov-square{
background-color: grey;
}
<div class="grid-container">c</div>

How to move HTML image to click location with JavaScript

I am trying to move an HTML image (a black marker) to where I click on a map on the screen. The map has an onclick function which makes a marker visible and move to where the user clicks. However at the moment this only works for the size of screen I am using and whenever the window size is changed the image is several hundred pixels off on each axis.
At the moment I am storing the coordinates of a click in an array and using DOM style.left/top to change the position and using those coordinates plus a set amount of pixels that works for me, but not any other screen.
I would like a way to have it it move wherever the user clicks, regardless of the page dimensions.
This is the current way I am doing things, with coords being the array containing the relative coordinates:
document.getElementById('black-marker').style.visibility = 'visible';
document.getElementById('black-marker').style.left = coords[0]-20;
document.getElementById('black-marker').style.top = coords[1]+205;
Click in the red box and the black circle will move to the mouse point.
I got the position of the red box and the mouse position and used them to calculate the relative position for the black box.
const blackMarker = document.getElementById('black-marker');
const parent = blackMarker.parentElement;
parent.addEventListener('click', e => {
const {
x,
y,
width,
height
} = parent.getBoundingClientRect();
blackMarker.style.left = `${(e.clientX - x) / width * 100}%`;
blackMarker.style.top = `${(e.clientY - y) / height * 100}%`;
});
body {
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
div {
position: relative;
width: 150px;
height: 150px;
background-color: red;
}
span {
position: absolute;
transform: translate(-50%, -50%);
width: 25px;
border-radius: 50%;
height: 25px;
background-color: black;
}
<div>
<span id="black-marker"></span>
</div>

Absolutely positioning an element along a path based on scroll position

I'm trying to position a div element over a sine wave that is used as its path. As the user scrolls, the callback should be able to calculate the left and top properties of the Ball along the sine wave. I'm having trouble with the positioning. I'm using Math.abs for the y-axis and I'm adding 8 (or -8) pixels to handle the x-axis.
Another thing I've noticed is that the scroll event listener callback sometimes missed certain breakpoints. I've console logged the scroll position and its true, the callback is either executed every ~3 pixels or the browser throttles the scroll event on its own for some reason (which I can understand, there's no point in tracking every pixel scroll).
Anyway, I'm wondering why my current approach isn't working and if there's a better solution to this problem? I feel like there's too much stuff going on and that this could be achieved in a better way. Here's what I have:
import React from "react";
import styled from "styled-components";
const lineHeight = 200;
export default React.memo(() => {
const [top, setTop] = React.useState(275);
const [left, setLeft] = React.useState(0);
const [previousPosition, setPreviousPosition] = React.useState(0);
React.useEffect(() => { const s = skrollr.init() }, []);
React.useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, [previousPosition, left]);
const handleScroll = React.useCallback(() => {
const pageYOffset = window.pageYOffset;
const isMovingForward = pageYOffset > previousPosition;
setPreviousPosition(window.pageYOffset);
setTop(lineHeight - Math.abs(lineHeight - pageYOffset));
if (isMovingForward) {
if (pageYOffset > 575 && pageYOffset <= 770) setLeft(left + 8);
} else {
if (pageYOffset <= 770 && pageYOffset >= 575) setLeft(left - 8)
}
}, [previousPosition, left]);
return (
<Main>
<Container
data-500p="transform: translateX(0%)"
data-1000p="transform: translateX(-800%)"
>
<Content>
<WaveContainer>
<Wave src="https://www.designcrispy.com/wp-content/uploads/2017/11/Sine-Wave-Curve.png" />
</WaveContainer>
<BallContainer top={top} left={left}>
<Ball />
</BallContainer>
</Content>
</Container>
</Main>
);
});
const Main = styled.div`
margin: 0;
padding: 0;
box-sizing: border-box;
`;
const Container = styled.div`
position: fixed;
width: 100%;
display: flex;
top: 0;
left: 0;
`;
const Content = styled.div`
min-width: 100vw;
height: auto;
display: grid;
place-items: center;
height: 100vh;
background: #fffee1;
font-weight: 900;
position: relative;
`;
const Wave = styled.img`
width: 600px;
`;
const WaveContainer = styled.div`
position: absolute;
left: -40px;
top: 45%;
`;
const Ball = styled.div`
width: 20px;
height: 20px;
border-radius: 50%;
background: black;
`;
const BallContainer = styled.div`
position: absolute;
transition: 0.25s;
${({ top, left }) => `top: ${top}px; left: ${left}px;`};
`;
I'm using Skrollr to handle the fixed canvas + scroll length.
Codesandbox
I changed your codesandbox so it does what you need: https://codesandbox.io/s/determined-nobel-64odf (I hope)
I should add that I changed various things about your code:
No need to set up new scroll listener every time a left or previous
position changes. What is all the code about previous position and
deciding if page is being scrolled up or down? I removed it.
The animation was being throttled due to transition: 0.25s on your
Ball container. In order to calculate the ball position relatively to
image - sinusoide, I moved them into the same container.
To calculate exact position of ball, WAVE_WIDTH and WAVE_HEIGHT
constants need to be used, and proper mathematics need to be used -
the sinusoide on image seems to be long of 2,5 periods. However 2,58
was better constant to fit the animation. I'd try using different
sinusoide and figure that out.
If you can support offset-path (not available on IE or Safari, apparently), I would strongly suggest moving as much of this as you can to CSS and SVG based animation. Here is a vanilla JS example - the only thing you actually have to calculate here is what percent along you want the animation to be, which could be based on any arbitrary scroll criteria:
const ball = document.querySelector('.ball');
window.addEventListener('scroll', () => {
const winHeight = document.body.offsetHeight;
const pageOffset = window.pageYOffset;
const pc = (pageOffset*2/winHeight)*100;
ball.style.offsetDistance = `${pc}%`;
});
* {
padding: 0;
margin: 0;
}
.page {
height: 200vh;
}
.container {
position: sticky;
top: 0;
width: 100%;
}
svg {
position: absolute;
fill: transparent;
stroke: blue;
stroke-width: 2px;
}
.ball {
offset-path: path("M10 80 C 40 10, 65 10, 95 80 S 150 150, 180 80");
width: 20px;
height: 20px;
background: black;
border-radius: 50%;
}
<div class="page">
<div class="container">
<svg>
<path d="M10 80 C 40 10, 65 10, 95 80 S 150 150, 180 80"/>
</svg>
<div class="ball"></div>
</div>
</div>
The drawbacks to this are that you'll have to recreate your specific path using SVG, which could take a bit of learning time if you aren't familiar with the syntax. The path is also not going to be responsive to screen width so you'd have to recalculate it a few times if that is important to you.
If that's not an option then you're basically going to have to write or find a script which is capable of generating a given sine wave, and then calculating coordinate position along it based on percentage. If it's not exact then it's never going to line up properly.

Is there any way to change a div size on scroll using intersectionObserver?

I'm trying to change the size (or scale) of a div while scrolling.
This div has a .8 scale attached to it css. I'd like to reach a scale of 1 progressively while scrolling.
IntersectionObserver seems to be a good choice to work with instead of scroll event but i don't know if i can change the state of an element using it.
You can change the scale of a div using.
document.getElementById("scaledDiv").style.transform = "scale(1)";
The scroll event should do what you want it to do. You can continue to add more if statements and check how many pixels they are scrolling to change it gradually to 1 or even back to 0.8 when they scroll back up. The 50 below represents 50 pixels from the top of the page.
window.onscroll = function() {
if (document.body.scrollTop > 50 || document.documentElement.scrollTop > 50) {
// They are scrolling past a certain position
document.getElementById("scaledDiv").style.transform = "scale(1)";
} else {
// They are scrolling back
}
};
I hope this will help you:
const container = document.querySelector('.container');
const containerHeight = container.scrollHeight;
const iWillExpand = document.querySelector('.iWillExpand');
container.onscroll = function(e) {
iWillExpand.style.transform = `scale(${0.8 + 0.2 * container.scrollTop / (containerHeight - 300)})`;
};
.container {
height: 300px;
width: 100%;
overflow-y: scroll;
}
.scrollMe {
height: 1500px;
width: 100%;
}
.iWillExpand {
position: fixed;
width: 200px;
height: 200px;
top: 10px;
left: 10px;
background-color: aqua;
transform: scale(0.8);
}
<div class='container'>
<div class='scrollMe' />
<div class='iWillExpand' />
</div>

Basic JavaScript: random positioning

I'm trying to create a square that will appear in a random place within a 300x300px space. It is currently moving horizontally but not vertically. Can someone help me get it to move vertically as well? Thank you!
#square {width: 200px;
height: 200px;
display: none;
position: relative;
}
var top=Math.random();
top=top*300;
var left=Math.random();
left=left*300;
document.getElementById("square").style.top=top+"px";
document.getElementById("square").style.left=left+"px";
Use left to translate horizontally and top for vertically.
const getRandom = (min, max) => Math.floor(Math.random()*(max-min+1)+min);
const square= document.querySelector('#square');
setInterval(() => {
square.style.left= getRandom(0, 300 - 200)+'px'; // 👈🏼 Horizontally
square.style.top = getRandom(0, 300 - 200)+'px'; // 👈🏼 Vertically
}, 500); // every 1/2 second
#space {
width: 300px;
height: 300px;
background-color: #eee
}
#square {
width: 200px;
height:200px;
position: relative;
background-color: #8e4435
}
<div id="space">
<div id="square">
</div>
</div>
If you remove the display: none it should be good, compare with this.
Also you could simplify with this:
var top = Math.random() * 300;

Categories