how can i create dialog effect like google keep.i tried to debug the css but without any success.
is there code example out there ?
i see that they using hidden position fixed modal that triggering on click but how they calculate the position.
This is what i could make out of your question(pure JS and CSS).
Below is the code
var example_note = document.getElementsByClassName('example_note')[0];
var close_btn = document.getElementById('close_btn');
example_note.onclick = function() {
document.getElementsByClassName('background_change')[0].style.display = "block";
document.getElementsByClassName('display_block')[0].style.display = "block";
example_note.style.display="none";
}
close_btn.onclick = function() {
document.getElementsByClassName('background_change')[0].style.display = "none";
document.getElementsByClassName('display_block')[0].style.display = "none";
example_note.style.display="block";
}
* {
margin: 0px;
padding: 0px;
font-family: 'arial';
}
.example_note {
position: absolute;
width: 250px;
margin-top: 10%;
margin-left: 15%;
box-shadow: -1px 1px 10px 3px rgba(0, 0, 0, 0.2);
-webkit-box-shadow: -1px 1px 10px 3px rgba(0, 0, 0, 0.2);
-moz-box-shadow: -1px 1px 10px 3px rgba(0, 0, 0, 0.2);
padding: 30px;
border-radius: 15px;
background-color: white;
}
.example_note h1 {
font-size: 23px;
}
.display_block {
display: none;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 450px;
background-color: white;
padding: 30px;
border-radius: 15px;
transform-origin: 0 25%;
animation: show_block 0.2s 1;
}
#keyframes show_block {
from {
transform: translate(-50%, -50%)scale(0);
}
to {
transform: translate(-50%, -50%)scale(1);
}
}
input[type="text"] {
width: 390px;
padding: 10px;
border: none;
}
input[type="text"]:focus {
outline: none;
}
button {
float: right;
background-color: white;
padding: 10px 20px 10px 20px;
border-radius: 8px;
border: none;
font-size: 17px;
transition: 0.4s;
font-weight: bold;
outline: none;
}
button:hover {
background-color: #E3E3E3;
}
.background_change {
display: none;
height: 100%;
width: 100%;
position: absolute;
background-color: black;
opacity: 0.6;
animation: show_back 0.5s 1;
}
#keyframes show_back {
from {
opacity: 0;
}
to {
opacity: 0.6;
}
}
<div class="example_note">
<h1>Example Note</h1>
</div>
<div class="background_change"></div>
<div class="display_block">
<input type="text" name="title" placeholder="Title" style="font-size: 25px;">
<br>
<input type="text" name="name" value="Example Note" style="font-size: 15px; font-weight: bold;">
<br>
<button id="close_btn">close</button>
</div>
This answer is plain JavaScript and CSS (no libraries), and produces the following effect:
A full working example is contained in the following snippet (best previewed full screen):
function openModal(noteEl, modalEl, modalContainerEl) {
// Compute and apply the transform to deform the modal to cover the note with a transition to make it animate
const transform = computeTransform(noteEl);
modalEl.style.transform = transform;
modalEl.style.transition = 'transform 250ms';
// Setup the modal background animate in too
modalContainerEl.style.backgroundColor = 'transparent';
modalContainerEl.style.transition = 'background-color 250ms';
// Show the modal
modalContainerEl.classList.add('modal-container--open');
// Put the rest in a setTimeout to allow the styles applied above to take
// affect and render before we overwrite them with new ones below
setTimeout(function () {
// Remove the transform to allow the modal to return to it's natural shape and position
modalEl.style.transform = 'none';
modalContainerEl.style.backgroundColor = 'rgba(33, 33, 33, 0.5)';
}, 0)
}
function computeTransform(noteEl) {
// Modal positions here are hardcoded to match styles set in CSS
const modalTop = 150;
const modalLeft = (document.body.offsetWidth / 2) - 300;
const modalWidth = 600;
const modalHeight = 150;
// Get note div's position relative to the viewport
const notePosition = noteEl.getBoundingClientRect();
// Compute a CSS transform that moves the modal to match the note's position
const translateX = notePosition.left - modalLeft;
const translateY = notePosition.top - modalTop;
const scaleX = notePosition.width / modalWidth;
const scaleY = notePosition.height / modalHeight;
return `translateX(${translateX}px) translateY(${translateY}px) scaleX(${scaleX}) scaleY(${scaleY})`;
}
// Handle click events using event delegation
document.addEventListener('click', function (event) {
// Handle click events on note elements (open modal)
if (event.target.className === 'note') {
// Get a reference
const modalContainerEl = document.querySelector('.modal-container');
const modalEl = document.querySelector('.modal');
openModal(event.target, modalEl, modalContainerEl);
}
// Handle click event on modal background element (close modal)
if (event.target.classList.contains('modal-container')) {
event.target.classList.remove('modal-container--open');
}
})
body {
display: flex;
flex-wrap: wrap;
width: 100%;
color: #333;
}
.note {
flex: 0 0 200px;
width: 200px;
height: 200px;
border: 1px solid #CCC;
margin: 12px;
border-radius: 10px;
}
.modal-container {
display: none;
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-color: rgba(33, 33, 33, 0.5);
}
.modal-container--open {
display: block;
}
.modal {
position: absolute;
top: 150px;
left: 50%;
margin-left: -300px;
width: 600px;
height: 150px;
transform-origin: top left;
will-change: transform; /* makes the animation run smoother */
background-color: #EEE;
border-radius: 10px;
}
<!DOCTYPE html>
<html>
<head>
<style type="text/css" />
</style>
<script type="text/javascript">
</script>
</head>
<body>
<div class="note">1</div>
<div class="note">2</div>
<div class="note">3</div>
<div class="note">4</div>
<div class="note">5</div>
<div class="note">6</div>
<div class="note">7</div>
<div class="note">8</div>
<div class="note">9</div>
<div class="note">10</div>
<div class="note">11</div>
<div class="note">12</div>
<div class="note">13</div>
<div class="note">14</div>
<div class="note">15</div>
<div class="note">16</div>
<div class="note">17</div>
<div class="note">18</div>
<div class="note">19</div>
<div class="note">20</div>
<div class="modal-container">
<div class="modal">
Modal
</div>
</div>
</body>
</html>
The trick is to apply a CSS transform to the modal in order to deform into the shape/position of the clicked note before showing the modal. We can then remove the transform and use a CSS transition to get it to smoothly animate into it's natural shape/position.
We calculate the transform as follows:
function computeTransform(noteEl) {
// Modal positions here are hardcoded to match styles set in CSS
const modalTop = 150;
const modalLeft = (document.body.offsetWidth / 2) - 300;
const modalWidth = 600;
const modalHeight = 150;
// Get note div's position relative to the viewport
const notePosition = noteEl.getBoundingClientRect();
// Compute a CSS transform that moves the modal to match the note's position
const translateX = notePosition.left - modalLeft;
const translateY = notePosition.top - modalTop;
const scaleX = notePosition.width / modalWidth;
const scaleY = notePosition.height / modalHeight;
return `translateX(${translateX}px) translateY(${translateY}px) scaleX(${scaleX}) scaleY(${scaleY})`;
}
And we apply it as follows:
function openModal(noteEl, modalEl, modalContainerEl) {
// Compute and apply the transform to deform the modal to cover the note with a transition to make it animate
const transform = computeTransform(noteEl);
modalEl.style.transform = transform;
modalEl.style.transition = 'transform 250ms';
// Setup the modal background animate in too
modalContainerEl.style.backgroundColor = 'transparent';
modalContainerEl.style.transition = 'background-color 250ms';
// Show the modal
modalContainerEl.classList.add('modal-container--open');
// Put the rest in a setTimeout to allow the styles applied above to take
// affect and render before we overwrite them with new ones below
setTimeout(function () {
// Remove the transform to allow the modal to return to it's natural shape and position
modalEl.style.transform = 'none';
modalContainerEl.style.backgroundColor = 'rgba(33, 33, 33, 0.5)';
}, 0)
}
Note the setTimeout between applying and removing the transform. This is important as otherwise the transform will never actually be applied.
See the snippet for the full details, but also of note: the transform-origin: top left; style on the modal is important to make the transform computation work.
I did like the example above, wich worked perfectly. I added opacity to de card selected to remove erase the card from the screen when it is clicked
<style type="text/css">
body {
display: flex;
flex-wrap: wrap;
width: 100%;
color: #333;
}
.note {
flex: 0 0 200px;
width: 200px;
height: 200px;
border: 1px solid #CCC;
margin: 12px;
border-radius: 10px;
}
.modal-container {
display: none;
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-color: rgba(33, 33, 33, 0.5);
}
.modal-container--open {
display: block;
}
.modal {
position: absolute;
top: 150px;
left: 50%;
margin-left: -300px;
width: 600px;
height: 150px;
transform-origin: top left;
will-change: transform;
/* makes the animation run smoother */
background-color: #EEE;
border-radius: 10px;
}
</style>
<script type="text/javascript">
function openModal(noteEl, modalEl, modalContainerEl) {
// Compute and apply the transform to deform the modal to cover the note with a transition to make it animate
const transform = computeTransform(noteEl);
modalEl.style.transform = transform;
modalEl.style.transition = 'transform 250ms';
// Setup the modal background animate in too
modalContainerEl.style.backgroundColor = 'transparent';
modalContainerEl.style.transition = 'background-color 250ms';
// Show the modal
modalContainerEl.classList.add('modal-container--open');
noteEl.style.opacity = 0;
// Put the rest in a setTimeout to allow the styles applied above to take
// affect and render before we overwrite them with new ones below
setTimeout(function () {
// Remove the transform to allow the modal to return to it's natural shape and position
modalEl.style.transform = 'none';
modalContainerEl.style.backgroundColor = 'rgba(33, 33, 33, 0.5)';
}, 0)
}
function computeTransform(noteEl) {
// Modal positions here are hardcoded to match styles set in CSS
const modalTop = 150;
const modalLeft = (document.body.offsetWidth / 2) - 300;
const modalWidth = 600;
const modalHeight = 150;
// Get note div's position relative to the viewport
const notePosition = noteEl.getBoundingClientRect();
// Compute a CSS transform that moves the modal to match the note's position
const translateX = notePosition.left - modalLeft;
const translateY = notePosition.top - modalTop;
const scaleX = notePosition.width / modalWidth;
const scaleY = notePosition.height / modalHeight;
return `translateX(${translateX}px) translateY(${translateY}px) scaleX(${scaleX}) scaleY(${scaleY})`;
}
// Handle click events using event delegation
let cardSelected;
document.addEventListener('click', function (event) {
// Handle click events on note elements (open modal)
if (event.target.className === 'note') {
// Get a reference
cardSelected = event.target
const modalContainerEl = document.querySelector('.modal-container');
const modalEl = document.querySelector('.modal');
openModal(event.target, modalEl, modalContainerEl);
}
// Handle click event on modal background element (close modal)
if (event.target.classList.contains('modal-container')) {
event.target.classList.remove('modal-container--open');
cardSelected.style.opacity = 1;
}
})
</script>
<div class="note">1</div>
<div class="note">2</div>
<div class="note">3</div>
<div class="note">4</div>
<div class="note">5</div>
<div class="note">6</div>
<div class="note">7</div>
<div class="note">8</div>
<div class="note">9</div>
<div class="note">10</div>
<div class="note">11</div>
<div class="note">12</div>
<div class="note">13</div>
<div class="note">14</div>
<div class="note">15</div>
<div class="note">16</div>
<div class="note">17</div>
<div class="note">18</div>
<div class="note">19</div>
<div class="note">20</div>
<div class="modal-container">
<div class="modal">
Modal
</div>
</div>
To create a modal, you can use a library called Swal. Swal, however does not look like keep's popups, so I've restyled it below.
script links you must reference to:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/#sweetalert2/theme-material-ui/material-ui.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"/>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2#10"></script>. You may also wish to visit Google Fonts and pick a nice font-family. Test this pop-up by clicking run this snippet.
const MySwal = Swal.mixin({
//background: "rgb(10,10,10)",
background: "white",
showCloseButton: true,
backdrop: "rgba(0,0,0,0.7)",
showClass: {
popup: "animate__animated animate__fadeInDown med"
},
hideClass: {
popup: "animate__animated animate__fadeOutUp fast"
},
width: "95vw"
});
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.js" integrity="sha512-HBD0cOZJYcymSn0H0CnN3VBhQLdiH8imucm16ZQ792TT2n48u6nmX+T7hZTCwmzIrgMt76x4rHhR7KkZqhIGxA==" crossorigin="anonymous"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<meta charset="UTF-8">
<script src="https://cdn.jsdelivr.net/npm/sweetalert2#10"></script>
<script src="alpha.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"/>
<link rel="shortcut icon" href="favicon.png" id="iconshort">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/#sweetalert2/theme-material-ui/material-ui.css">
<script src='https://kit.fontawesome.com/a076d05399.js'></script>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght#300&display=swap" rel="stylesheet">
</head>
<body>
<button onclick="MySwal.fire('Title','Content')">LAUNCH POP-UP</button>
</body>
const MySwal = Swal.mixin({
//background: "rgb(10,10,10)",
background: "white",
showCloseButton: true,
backdrop: "rgba(0,0,0,0.7)",
showClass: {
popup: "animate__animated animate__fadeInDown med"
},
hideClass: {
popup: "animate__animated animate__fadeOutUp fast"
},
width: "95vw",
willOpen: function() {
open_audio.play();
},
willClose: function() {
confirm_audio.play();
}
});
Related
The aside element is a modal popup with multiple a href links inside, none of which are working. I can right click and go to the link but clicking on it does nothing. The open modal is triggered by Javascript. There are no external libraries, plugins etc that could be causing conflict.
What am I missing here?
const overlay = document.querySelector(".overlay");
const card = document.querySelectorAll(".card");
card.forEach((card) => {
card.addEventListener("click", () => {
const cardId = card.getAttribute("data-card");
const modal = document.getElementById(`${cardId}`);
modal.classList.remove("hidden");
overlay.classList.remove("hidden");
const vScrollPos = window.scrollY;
document.body.style.position = "fixed";
document.body.style.top = `-${vScrollPos}px`;
const closeModal = function() {
modal.classList.add("hidden");
overlay.classList.add("hidden");
const scrollY = document.body.style.top;
document.body.style.position = "";
document.body.style.top = "";
window.scrollTo(0, parseInt(scrollY || "0") * -1);
};
const modalCloseBtn = modal.querySelector(".close-modal");
modalCloseBtn.addEventListener("click", () => {
closeModal();
});
overlay.addEventListener("click", closeModal);
document.addEventListener("keydown", function(e) {
if (e.key === "Escape" && !modal.classList.contains("hidden")) {
closeModal();
}
});
});
});
.hidden {
display: none;
}
.modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: calc(100vw - 16rem);
overflow-y: scroll;
max-height: calc(100vh - 16rem);
background-color: white;
padding: 6rem;
border-radius: 5px;
box-shadow: 0 3rem 5rem rgba(0, 0, 0, 0.3);
z-index: 10;
pointer-events: all;
cursor: pointer;
}
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(3px);
overflow-y: scroll;
z-index: 5;
}
.modal-title {
font-size: 2.4rem;
margin-bottom: 3rem;
}
.close-modal {
position: absolute;
top: 1.2rem;
right: 2rem;
font-size: 5rem;
color: #333;
cursor: pointer;
border: none;
background: none;
}
<article class="card" data-card="modal1">
<img class="card-img" src="img/card-thumb/cardImg.jpg" alt="card image">
<p class="card-title">open card modal</p>
</article>
<aside class="modal hidden" id="modal1">
<button class="close-modal">×</button>
<h5 class="modal-title">
<a href='https://www.google.com/' target='_blank' rel='noreferrer noopener nofollow'>WHY IS THIS LINK NOT OPENING?</a>
<h5>
</aside>
Ok. issue has been found and fixed.
I was using scrollIntoView to handle page nav and smooth scrolling and I imprecisely targeted all "a" elements before I wrote the code for this current section with "a" elements inside of modal containers. Solved issue by giving the page nav a's an additional class then targeting that class only for that section of JS code! Doooooh.
I have a custom cursor on my site that is working perfectly apart from one thing. When clicking through to a new page, when the page loads the cursor resets itself to the top left of the page regardless of where you leave the mouse on the page, then once you moved the mouse the cursor moves back to where the mouse is. I have tried removing "top" & "left" from the CSS but the problem remains. I cant see what is causing this to happen, and I just need the cursor to stay where the mouse is positioned on the page and not reset every time you navigate to a new page.
jQuery(document).ready(function($) {
let cursor = document.querySelector('#custom-cursor');
if(/Android|webOS|iPhone|iPad|iPod|BlackBerry|Windows Phone/i.test(navigator.userAgent)) {
$('#custom-cursor').remove();
}
else { cursor.style.display = 'block';}
document.addEventListener('mousemove', evt => {
let { clientX: x, clientY: y } = evt;
let scale = 1;
if (evt.target.matches('a,span,[onclick],img,video,i')) {
cursor.classList.add('active');
scale = 0.5;
} else {
cursor.classList.remove('active');
}
cursor.style.transform = `translate(${x}px, ${y}px) scale(${scale})`;
});
});
* {
cursor: none;
}
#custom-cursor {
display: none;
position: fixed;
width: 20px; height: 20px;
top: -10px;
left: -10px;
border: 2px solid black;
border-radius: 50%;
opacity: 1;
background-color: #fb4d98;
pointer-events: none;
z-index: 99999999;
transition:
transform ease-out 0.15s,
border 0.5s,
opacity 0.5s,
background-color 0.5s;
}
#custom-cursor.active {
opacity: 0.5;
background-color: #000;
border: 2px solid #fb4d98;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="custom-cursor"></div>
Use ordinary CSS cursor as shown in the other answer and replace it with you fancy cursor in the first mouse event:
jQuery(document).ready(function($) {
let cursor = document.querySelector('#custom-cursor');
document.addEventListener('mousemove', evt => {
document.body.classList.add('custom-cursor-moved')
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|Windows Phone/i.test(navigator.userAgent)) {
$('#custom-cursor').remove();
} else {
cursor.style.display = 'block';
}
let {
clientX: x,
clientY: y
} = evt;
let scale = 1;
if (evt.target.matches('a,span,[onclick],img,video,i')) {
cursor.classList.add('active');
scale = 0.5;
} else {
cursor.classList.remove('active');
}
cursor.style.transform = `translate(${x}px, ${y}px) scale(${scale})`;
});
});
body {
height: 100vh;
}
html,
body {
margin: 0;
padding: 0;
}
* {
cursor: url(https://i.stack.imgur.com/7pmmV.png) 0 0, auto;
}
.custom-cursor-moved,
.custom-cursor-moved * {
cursor: none !important;
}
#custom-cursor {
display: none;
position: fixed;
width: 20px;
height: 20px;
top: -10px;
left: -10px;
border: 2px solid black;
border-radius: 50%;
opacity: 1;
background-color: #fb4d98;
pointer-events: none;
z-index: 99999999;
transition: transform ease-out 0.15s, border 0.5s, opacity 0.5s, background-color 0.5s;
}
#custom-cursor.active {
opacity: 0.5;
background-color: #000;
border: 2px solid #fb4d98;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="custom-cursor"></div>
Try me.<br> Try me.
It needs a bit of modifications (better cursor image, fix it hotspot etc.) but it works.
Be very, very careful when doing such thing. Try to not break any accessibility tools and please do not assume that Android/some specific user-agent HAS touchscreen, etc.. Use proper APIs.
Use CSS cursor property instead:
html {
cursor: url(https://cdn.sstatic.net/Sites/stackoverflow/Img/favicon.ico?v=ec617d715196) 0 0, auto;
height: 100%;
}
Try me.
I am trying to a make carousel using pure Javascript. I successfully manage to slide the carousel and have created left and right buttons.
I took my slide functions and added them to the button on-click event-listener, but I have problems when I implement the function on my buttons. It does not behave as expected. My code is below, how can I fix this?
const images = document.getElementById('imgs'); //here
const allImages = document.querySelectorAll('#imgs img');
const leftBtn = document.getElementById('left');
const rightBtn = document.getElementById('right');
let index = 0;
function changeSliderPage() {
const dot = [...document.getElementsByClassName('star')];
index++;
if (index > allImages.length - 1) {
index = 0
}
imgs.style.transform = `translateX(${-index * 500}px)`;
dot.forEach((dot, i) => {
if (i === index) {
dot.classList.add('active')
} else {
dot.classList.remove('active')
}
});
};
allImages.forEach(i => {
const elem = document.createElement('div');
elem.classList.add('star');
document.body.appendChild(elem)
});
rightBtn.onclick = () => {
changeSliderPage(index + 1);
}
leftBtn.onclick = () => {
changeSliderPage(index - 1);
}
let x = setInterval(changeSliderPage, 100000);
images.onmouseover = () => {
clearInterval(x)
}
images.onmouseout = () => {
x = setInterval(changeSliderPage, 2000);
}
*{
box-sizing: border-box;
}
body {
margin: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.carousel {
overflow: hidden;
width: 500px;
height: 500px;
box-shadow: 2px 2px 5px rgba(0, 0, 0, .3);
border-radius: 5px;
}
.image-container {
display: flex;
transition: transform 300ms linear;
transform: translateX(0);
}
img {
width:500px;
height: 500px;
object-fit: cover;
}
.star{
cursor: pointer;
height: 15px;
width: 15px;
margin: 0 10px;
border-radius: 50%;
display: inline-block;
transition: background-color 0.6s ease;
background-color: #eeeeee;
}
.star.active{
background-color: red;
}
button{
cursor: pointer;
position: relative;
font-size: 18px;
transition: 0.6s ease;
user-select: none;
height: 50px;
width: 40px;
display: flex;
justify-content: center;
align-items: center;
align-content: center;
top: calc(50% - 25px);
}
button:hover {
background-color: rgba(0,0,0,0.8);
};
button.left {
border-radius: 3px 0 0 3px;
right: 0;
}
button.left {
border-radius: 3px 0 0 3px;
left: 0;
}
<button id="left">❮</button>
<button id="right">❯</button>
<div class="carousel">
<div class="image-container" id="imgs" >
<img src="https://images.unsplash.com/photo-1599736375341-51b0a848f3c7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=60" alt="">
<img src="https://images.unsplash.com/photo-1516026672322-bc52d61a55d5?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=60" alt="">
<img src="https://images.unsplash.com/photo-1573081586928-127ecc7948b0?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=60" alt="">
<img src="https://images.unsplash.com/flagged/photo-1572850005109-f4ac7529bf9f?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=60" alt="">
</div>
</div>
Logic that I use with carousels:
for example you have 4 images:
[1][2][3][4]
I have an animation for sliding every image, I add 5th image which is same as image no 1:
[1][2][3][4][1]
Imagine cursor which shows what image is currently displayed, Ill mark cursor as ! !
So at begin:
[!1!][2][3][4][1]
Now the slider moves on...
[1][!2!][3][4][1]
etc...
It moves to last image:
[1][2][3][4][!1!]
And now it has to move under the hood from last image to first image, but without any animation so the whole change is not visible by user:
[!1!][2][3][4][5]
This way you can get inifinite carousel, just need to check in javascript if current image is last one and you want to slide right -> no animation. Same if you are on 1st image and want to slide left.
I am playing around and practice my JS (beginner). I have created stacking panels and i hit a wall. I just can not target and add class of shadow to the moving element (only when one panel is above other, class should add).
For now i have this:
const boxes = Array.from(document.querySelectorAll(".box"));
const wrapper = document.querySelector(".wrapper");
const leftMargin = 60; //px
function scrollWrap(e) {
let scrollCoord = wrapper.scrollLeft; // horizontal scroll value
boxes.forEach((box, index) => {
let leftMarginStop = index * leftMargin;
const boxCoord = box.getBoundingClientRect();
let leftCoordPanel = boxCoord.left;
let flag = false;
//console.log({scrollCoord, leftMarginStop, leftCoordPanel, box});
if (boxCoord.left <= leftMarginStop) { // if left side of pannel is less than margin 60, 120, 180,...
//console.log("STAHP!!");
box.style.position = "sticky";
box.style.left = `${leftMarginStop}px`; // sets the left to 60, 120, 180,...
flag = true;
if (flag) {
box.classList.add("shadow");
console.log(this) //how to target each panel rather than wrapper?
} else {
box.classList.remove("shadow");
}
} else {
box.style.position = "static";
box.style.left = 0;
flag = false;
}
});
}
wrapper.addEventListener("scroll", scrollWrap);
body {
margin: 0;
padding: 0;
}
.wrapper, .box1, .box2, .box3, .box4, .box5, .box6, .box7, .box8 {
position: sticky;
height: 750px;
z-index: 1;
}
.wrapper {
width: 1442px;
border-right: 1px solid #f2f2f2;
border-bottom: 1px solid #f2f2f2;
display: flex;
overflow: scroll;
}
.wrapper .box1 {
min-width: 357px;
height: 750px;
background-color: #1a1a1a;
}
.wrapper .box2 {
min-width: 357px;
height: 750px;
background-color: #333;
}
.wrapper .box3 {
min-width: 702px;
height: 750px;
background-color: #4d4d4d;
}
.wrapper .box4 {
min-width: 630px;
height: 750px;
background-color: #666;
}
.wrapper .box5 {
min-width: 630px;
height: 750px;
background-color: #808080;
}
.wrapper .box6 {
min-width: 357px;
height: 750px;
background-color: #999;
}
.wrapper .box7 {
min-width: 630px;
height: 750px;
background-color: #b3b3b3;
}
.wrapper .box8 {
min-width: 630px;
height: 750px;
background-color: #ccc;
}
.shadow {
box-shadow: -4px 0px 40px rgba(0, 0, 0, 0.2);
}
<div class="wrapper">
<div class="box box1"></div>
<div class="box box2"></div>
<div class="box box3"></div>
<div class="box box4"></div>
<div class="box box5"></div>
<div class="box box6"></div>
<div class="box box7"></div>
<div class="box box8"></div>
</div>
If anyone can help me out, please show me code with explanation, so i can see and know what and how.
I try to do it with some flag or without, but every time the class is added when element reaches the end (left padding). I want to add class when element is on top of other element. If it is not, remove class. Also, how to make this so it works on mouse scroll wheel? I was testing this with apple magic mouse, but on scroll wheel it does not work.
Oh and please, if you see something very wrong please let me know, i am beginner and would like to learn something from this post.
For now i have managed to update my code. It works when i scroll and shadow is also applying to the panel. There is still something i wonder.
When i add shadow class i created transition. It works perfect, but when i remove shadow class it just disappears, no transition back. Why is that? I want shadow to appear and disappear in same way
How to track every single panel if it has reached the left margin, and than apply eventListener with mouse enter and mouse leave on it? So when i hover to "closed" panel, i get that item and if i hover on NOT "closed" panel i will not get item. I was trying with console.log "this" but it returned every panel i mouse entered it
How to target every panel, so i can later say, when 4th panel reaches left margin, the margin of stacked elements change?
My updated code:
const boxes = Array.from(document.querySelectorAll(".box"));
const wrapper = document.querySelector(".wrapper");
const leftMargin = 60; //px
function scrollWrap(e) {
let scrollCoord = wrapper.scrollLeft; // horizontal scroll value
boxes.forEach((box, index) => {
let leftMarginStop = (index) * leftMargin; // calculation for left margin stop (60, 120, 180,...)
const boxCoord = box.getBoundingClientRect();
const leftSideOfCurrent = boxCoord.left; // coordinarion of left side of panel
const rightSideOfCurrent = boxCoord.right; // coordinarion of right side of panel
const leftSideOfNextItem = box.nextElementSibling.getBoundingClientRect().left; // coordinarion of left side of NEXT panel
box.style.position = "sticky";
box.style.left = `${leftMarginStop}px`;
// controll shadow of first element
scrollCoord > 0 ? boxes[1].classList.add("shadow") : boxes[1].classList.remove("shadow");
// controll shadow of all 0+ elements
if (leftSideOfCurrent <= leftMarginStop) { // if left side of pannel is less than margin 60, 120, 180,...
box.nextElementSibling.classList.add("shadow");
}
// controll removal of shadow of all 0+ elements
if (leftSideOfNextItem === rightSideOfCurrent) {
box.nextElementSibling.classList.remove("shadow");
}
// when panel 5 reach left margin, left margin change from 60 to 30 to all panels
if (boxes[4] && leftSideOfCurrent < leftMarginStop) {
console.log("helo");
}
});
}
wrapper.addEventListener("scroll", scrollWrap);
body {
margin: 0;
padding: 0;
}
.wrapper, .box1, .box2, .box3, .box4, .box5, .box6, .box7, .box8 {
position: sticky;
height: 750px;
z-index: 1;
}
.wrapper {
width: 1442px;
border-right: 1px solid #f2f2f2;
border-bottom: 1px solid #f2f2f2;
display: flex;
overflow: scroll;
}
.wrapper .box0 {
min-width: 357px;
background-color: #1a1a1a;
}
.wrapper .box1 {
min-width: 357px;
background-color: #333;
}
.wrapper .box2 {
min-width: 702px;
background-color: #4d4d4d;
}
.wrapper .box3 {
min-width: 630px;
background-color: #666;
}
.wrapper .box4 {
min-width: 630px;
background-color: #808080;
}
.wrapper .box5 {
min-width: 357px;
background-color: #999;
}
.wrapper .box6 {
min-width: 630px;
background-color: #b3b3b3;
}
.wrapper .box7 {
min-width: 630px;
background-color: #ccc;
}
.shadow {
box-shadow: -4px 0px 40px rgba(0, 0, 0, 1.2);
-webkit-transition: box-shadow 0.2s cubic-bezier(0.4,-0.01, 0, 0.98);
-moz-transition: box-shadow 0.2s cubic-bezier(0.4,-0.01, 0, 0.98);
-o-transition: box-shadow 0.2s cubic-bezier(0.4,-0.01, 0, 0.98);
transition: box-shadow 0.2s cubic-bezier(0.4,-0.01, 0, 0.98);
}
<div class="wrapper">
<div class="box box0"></div>
<div class="box box1"></div>
<div class="box box2"></div>
<div class="box box3"></div>
<div class="box box4"></div>
<div class="box box5"></div>
<div class="box box6"></div>
<div class="box box7"></div>
</div>
Tips for your code:
In your CSS code, the wrapper element has a width property. This
causes the page and the wrapper element to have a scrollbars. So,
remove it.
You do not need to use javascript to add stickiness to the
boxes. The only CSS will do this for you. You need JavaScript only
for add the left property and shadow to the boxes.
Don’t try to use overflow: auto|scroll|hidden on the parent element
of a position:sticky element. It completely breaks the stickiness.
overflow: visible is fine. See the code snippet below.
In your JavaScript code, the flag variant value, always is true, So
the shadow class can't remove from elements.
Other notes:
If you’re wanting to use position:absolute on an element inside of
a sticky element you have to be careful. If your app is running in
an older browser that doesn’t support position:sticky, then that
sticky element won’t act like a relative positioned element. So
the absolute positioned element will skip it and look up the DOM
tree until it finds the next non-static element (absolute / relative / fixed position), defaulting to the html element if none
found. In other words, your absolute positioned element is going
to be in a way different place on the screen than you expected it to
be.
position: sticky; is supported in a lot of browsers, but not yet
in Edge. IE doesn’t matter at this point. There are many
polyfills out there if you absolutely have to have this behavior,
but they all use JavaScript. A better option is to design your app
so that sticky position is a slick addition, but the app still
functions without it.
Example:
const boxes = Array.from( document.querySelectorAll( '.box' ) ),
scroller = document.querySelector( '.scroller' ),
leftMargin = 30,
length = boxes.length - 1;
function scrollWrap() {
boxes.forEach( function( box, index ) {
let leftMarginStop = index * leftMargin;
const boxCoord = box.getBoundingClientRect();
let leftCoordPanel = boxCoord.left;
if ( leftCoordPanel <= leftMarginStop ) {
box.style.left = leftMarginStop + 'px';
if ( index < length ) boxes[ index + 1 ].classList.add( 'shadow' )
if ( index == 0 && boxes[ 1 ].getBoundingClientRect().left == box.offsetWidth ) boxes[ 1 ].classList.remove( 'shadow' )
} else {
box.style.left = 0;
if ( index < length ) boxes[ index + 1 ].classList.remove( 'shadow' )
}
} );
}
scroller.addEventListener( 'scroll', scrollWrap )
html,
body {
margin: 0;
height: 100%
}
.scroller {
height: 100%;
display: flex;
align-items: center;
overflow-x: auto;
overflow-y: hidden
}
.wrapper {
height: 100%;
display: flex
}
.box {
min-width: 630px;
height: 100%;
position: -webkit-sticky;
position: sticky;
left: 0 /* <-- become sticky once touching left edge */
}
.box1 {
min-width: 357px;
background: #1a1a1a url(https://cdn.pixabay.com/photo/2015/12/10/16/09/seamless-pattern-1086662__340.jpg)
}
.box2 {
min-width: 357px;
background: #333 url(https://cdn.pixabay.com/photo/2015/12/18/23/46/template-1099298__340.png)
}
.box3 {
min-width: 702px;
background: #4d4d4d url(https://cdn.pixabay.com/photo/2014/07/28/16/00/pattern-403769__340.png)
}
.box4 {
background: #666 url(https://cdn.pixabay.com/photo/2017/06/10/03/14/damask-2388884__340.png)
}
.box5 {
background: #808080 url(https://cdn.pixabay.com/photo/2015/12/12/17/34/seamless-pattern-1089797__340.png)
}
.box6 {
min-width: 357px;
background: #999 url(https://cdn.pixabay.com/photo/2016/03/06/16/23/background-1240686__340.png)
}
.box7 {
background: #b3b3b3 url(https://cdn.pixabay.com/photo/2018/04/24/05/00/backdrop-3346304__340.png)
}
.box8 {
background: #ccc url(https://cdn.pixabay.com/photo/2016/04/01/09/03/floral-1299131__340.png)
}
.shadow {
box-shadow: -10px 0px 40px rgba(0, 0, 0, 1);
}
<div class="scroller">
<div class="wrapper">
<div class="box box1"></div>
<div class="box box2"></div>
<div class="box box3"></div>
<div class="box box4"></div>
<div class="box box5"></div>
<div class="box box6"></div>
<div class="box box7"></div>
<div class="box box8"></div>
</div>
</div>
About your updated code:
the following line in your jS code:
const leftSideOfNextItem = box.nextElementSibling.getBoundingClientRect().left;
causes an error. Because, when index become 8, box.nextElementSibling can't retrieve any element. So you can change it to this:
const leftSideOfNextItem = ( index < boxes.length - 1 ) ? box.nextElementSibling.getBoundingClientRect().left : 0;
Also at the end of your JS code, the following code snippet:
if (index[4] && leftSideOfCurrent < leftMarginStop) {
console.log("helo");
}
must change to:
// when panel 5 reach left margin, left margin change from 60 to 30 to all panels
if (index == 4 && leftSideOfCurrent <= leftMarginStop) {
console.log("helo");
leftMargin = 30
}
Also you must change const leftMargin = 60; to var leftMargin = 60;
Updated code:
const boxes = Array.from(document.querySelectorAll(".box"));
const wrapper = document.querySelector(".wrapper");
var leftMargin = 60; //px
function scrollWrap(e) {
let scrollCoord = wrapper.scrollLeft; // horizontal scroll value
boxes.forEach((box, index) => {
let leftMarginStop = (index) * leftMargin; // calculation for left margin stop (60, 120, 180,...)
const boxCoord = box.getBoundingClientRect();
const leftSideOfCurrent = boxCoord.left; // coordinarion of left side of panel
const rightSideOfCurrent = boxCoord.right; // coordinarion of right side of panel
const leftSideOfNextItem = ( index < boxes.length - 1 ) ? box.nextElementSibling.getBoundingClientRect().left: 0; // coordinarion of left side of NEXT panel
box.style.position = "sticky";
box.style.left = `${leftMarginStop}px`;
// controll shadow of first element
scrollCoord > 0 ? boxes[1].classList.add("shadow") : boxes[1].classList.remove("shadow");
// controll shadow of all 0+ elements
if (leftSideOfCurrent <= leftMarginStop) { // if left side of pannel is less than margin 60, 120, 180,...
box.nextElementSibling.classList.add("shadow");
}
// controll removal of shadow of all 0+ elements
if (leftSideOfNextItem === rightSideOfCurrent) {
box.nextElementSibling.classList.remove("shadow");
}
// when panel 5 reach left margin, left margin change from 60 to 30 to all panels
if (index == 4 && leftSideOfCurrent <= leftMarginStop) {
console.log("helo");
leftMargin = 30
}
});
}
wrapper.addEventListener("scroll", scrollWrap);
body {
margin: 0;
padding: 0;
}
.wrapper, .box0, .box1, .box2, .box3, .box4, .box5, .box6, .box7 {
position: sticky;
height: 750px;
z-index: 1;
}
.wrapper {
width: 1442px;
border-right: 1px solid #f2f2f2;
border-bottom: 1px solid #f2f2f2;
display: flex;
overflow: scroll;
}
.wrapper .box0 {
min-width: 357px;
background-color: #1a1a1a;
}
.wrapper .box1 {
min-width: 357px;
background-color: #333;
}
.wrapper .box2 {
min-width: 702px;
background-color: #4d4d4d;
}
.wrapper .box3 {
min-width: 630px;
background-color: #666;
}
.wrapper .box4 {
min-width: 630px;
background-color: #808080;
}
.wrapper .box5 {
min-width: 357px;
background-color: #999;
}
.wrapper .box6 {
min-width: 630px;
background-color: #b3b3b3;
}
.wrapper .box7 {
min-width: 630px;
background-color: #ccc;
}
.shadow {
box-shadow: -4px 0px 40px rgba(0, 0, 0, 1.2);
-webkit-transition: box-shadow 0.2s cubic-bezier(0.4,-0.01, 0, 0.98);
-moz-transition: box-shadow 0.2s cubic-bezier(0.4,-0.01, 0, 0.98);
-o-transition: box-shadow 0.2s cubic-bezier(0.4,-0.01, 0, 0.98);
transition: box-shadow 0.2s cubic-bezier(0.4,-0.01, 0, 0.98);
}
/* To make the console visible. */
.box0 {
height: 700px;
)
<div class="wrapper">
<div class="box box0"></div>
<div class="box box1"></div>
<div class="box box2"></div>
<div class="box box3"></div>
<div class="box box4"></div>
<div class="box box5"></div>
<div class="box box6"></div>
<div class="box box7"></div>
</div>
This was helpful. I know for error but could not fix it. I have set let leftMargin = 60; i use let instead var.
I am working on the margins, if the 5th panel reaches the left margin the margin of all panels get 30px. I did it also for reverse:
if (index > 4 && leftSideOfCurrent <= leftMarginStop) {
leftMargin = 30;
} else if (index < 5 && leftSideOfCurrent > leftMarginStop) {
leftMargin = 60;
}
But now when 5th panel reaches left margin, the panels less than 5 get EACH transitioned seperately instead of all, but when the 5th element is no longer in left margin, all gets transitioned at same time. Why is it like that? I do not understand. Can you explain please?
please can some one help to figure out why the image not maintaining the aspect ratio. I've was looking for a Flickr script to my site and found one. But the main problem is that it cannot maintain the aspect ratio.
Please help me obi wan, you are my last hope!
Script was found in this site:
https://github.com/blackfalcon/flickr-carousel/blob/master/index.html
To help identify the issues i've uploaded it to my site as index2:
http://scorpion3d.com/index2.html
<!doctype html>
<html lang="en" dir="ltr">
<head>
<title>Flickr API Carousel</title>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link href="https://fonts.googleapis.com/css?family=EB+Garamond" rel="stylesheet">
<style>
html,
body {
height: 100%;
margin: 0;
font-family: 'EB Garamond', serif;
}
html {
box-sizing: border-box;
}
body {
background-color: rgba(0,0,0,0.5);
}
*, *:before, *:after {
box-sizing: inherit;
}
a {
color: white;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
.carousel-box {
height: 100%;
overflow: hidden;
}
.action-buttons {
width: 100%;
display: flex;
justify-content: space-between;
position: absolute;
top: calc(50% - 100px);
z-index: 1;
opacity: .1;
transition: opacity .25s ease;
}
.carousel-box:hover .action-buttons {
opacity: 1;
}
.action-buttons button {
border: none;
font-size: 50px;
padding: 0 10px;
display: block;
background-color: rgba(255,255,255,.5);
height: 1.2em;
line-height: 1;
}
.action-buttons button:hover {
cursor: pointer;
}
.carousel-content {
height: 100%;
display: flex;
align-items: center;
}
[data-function="slide"] {
position: absolute;
width: 100%;
height: 100%;
text-align: center;
opacity: 0;
transition: opacity .25s ease;
display: flex;
align-items: flex-end;
}
[data-function="slide"] p {
letter-spacing: 3px;
padding: 1rem 40px;
width: 100%;
color: white;
font-size: 2.5rem;
z-index: 10;
margin: 0 auto;
background-color: rgba(0,0,0,0.3);
}
.carousel-content img {
position: fixed;
top: 0;
bottom: 0;
left: -90%;
right: 0;
height: 100%;
overflow: auto;
}
[data-function="slide"].current-slide {
opacity: 1;
}
.album-info {
color: white;
position: absolute;
background-color: rgba(0,0,0,0.3);
font-size: 1rem;
z-index: 10;
width: 100%;
padding: 1rem;
}
.album-info > a {
margin-right: 1rem;
padding-right: 1rem;
border-right: 1px solid white;
}
#media screen and (min-width: 568px) {
.carousel-content img {
left: 0;
width: 100%;
overflow: auto;
}
}
#media screen and (min-width: 768px) {
.action-buttons button {
font-size: 100px;
}
}
</style>
</head>
<body>
<div class="carousel-box" id="carouselBox">
<div class="action-buttons">
<button class="prev" aria-hidden="true" tabindex="-1" title="use left arrow key">
❮
</button>
<button class="next" aria-hidden="true" tabindex="-1" title="use right arrow key">
❯
</button>
</div>
<div class="album-info">
<a id="albumLink" target="_blank"><span id="albumInfo"></span></a>
<span>Photos by <a id="albumOwnerLink" target="_blank"><span id="owner"></span></a></span>
</div>
<div class="carousel-content" id="carouselContent"></div>
</div>
<script type="text/javascript">
// Flickr configurations
// Obfuscated API key var for demo
const _0x6e6e=["\x65\x66\x63\x38\x33\x64\x63\x63\x64\x37\x63\x31\x64\x30\x61\x65\x39\x33\x66\x34\x61\x61\x37\x61\x66\x62\x39\x37\x31\x66\x63\x65"];const apiKey=_0x6e6e[0]
// To personalize app, replace with your own API key
// const apiKey = '';
const album = '72157688964206172',
albumOwner = '154845055#N05',
flickrUrl = 'https://api.flickr.com/services/rest/',
method = '?method=flickr.photosets.getPhotos&api_key=',
perPage = '20',
formatCallback = '&format=json&nojsoncallback=1',
contentContainer = document.getElementById('carouselContent'),
oReq = new XMLHttpRequest();
// Handle a response from the Flickr API
function reqListener () {
const flickrPhotos = JSON.parse(this.responseText);
console.log(flickrPhotos.photoset);
// Parse response for album and owner information
const ownerName = flickrPhotos.photoset.ownername,
albumTitle = flickrPhotos.photoset.title,
albumUrl = 'https://www.flickr.com/photos/' + albumOwner + '/albums/' + album,
albumOwnerUrl = 'https://www.flickr.com/photos/' + albumOwner;
// append response data to HTML DOM elements
albumInfo.innerHTML = albumTitle;
owner.innerHTML = ownerName;
albumLink.href = albumUrl;
albumOwnerLink.href = albumOwnerUrl;
// Iterate through flickrPhotos in the response
flickrPhotos.photoset.photo.forEach(function(foto) {
// Generate the URL for individual photo based on template
const url = 'https://farm' + foto.farm + '.staticflickr.com/' + foto.server + '/' + foto.id + '_' + foto.secret + '.jpg';
const photoTitle = foto.title;
// Generate the necessary slide markup
// <span data-function="slide">
// <p>title</p>
// <img src="" />
// </span>
const span = document.createElement('span'),
img = document.createElement('img'),
title = document.createElement('p');
// append response data to generated HTML DOM elements
img.src = url;
img.alt = photoTitle;
title.innerHTML = photoTitle;
span.dataset.function = 'slide';
span.appendChild(title);
span.appendChild(img);
// Now append the new slide to the slide container
contentContainer.appendChild(span);
});
// Remote API request has been made and processed, initialize the carousel.
flickrCarousel();
}
// API call to Flickr
oReq.addEventListener("load", reqListener);
oReq.open("GET", flickrUrl + method + apiKey + '&photoset_id=' + album + '&user_id=' + albumOwner + '&per_page=' + perPage + formatCallback);
oReq.send();
// Carousel function
function flickrCarousel () {
// set scoped variables
const carouselBox = document.getElementById('carouselBox'),
prev = carouselBox.querySelector('.prev'),
next = carouselBox.querySelector('.next'),
slides = carouselBox.querySelectorAll('[data-function=slide]'),
deck = slides.length;
let slide = 0,
currentSlide = slides[0];
// Find current slide of array and add selector
currentSlide.classList.add('current-slide');
// slider function
function pushSlide(flip) {
// Use value of array to find node and remove selector
currentSlide.classList.remove('current-slide');
// Using value of current slide, add flip value to determine next slide value
slide = slide + flip;
// allows for full rotation of carousel; if 0 set value to -1 of array length
if (flip === -1 && slide < 0) {
slide = deck - 1;
}
// allows for full rotation of carousel; if max length of array, set to 0
if (flip === 1 && !slides[slide]) {
slide = 0;
}
// determine active slide and add selector
currentSlide = slides[slide];
currentSlide.classList.add('current-slide');
}
// Bind click events to toggle buttons and pass in slide flip value
next.addEventListener('click', () => {
pushSlide(1);
});
prev.addEventListener('click', () => {
pushSlide(-1);
});
// Bind keyboard events to slide triggers
document.addEventListener('keydown', event => {
if( event.keyCode == 39 ) {
pushSlide(1);
}
if( event.keyCode == 37 ) {
pushSlide(-1);
}
});
};
</script>
</body>
</html>
You have mentioned the height of the image to be 100% while defining ".carousel-content img" class.Try as below :
.carousel-content img {
position: fixed;
top: 0;
bottom: 0;
left: -90%;
right: 0;
overflow: auto;
}
Edit
The general thing to be kept in mind while working with the image :
we must not change the proportion of the image to prevent it from being distorted. hence, we can work either on width or on height, keeping the other one set to "Auto".
Definitely, the image will settle itself according to what we have defined for its width and height. So if there won't be enough space to accommodate the whole image, the part of it will be cropped of ( or you say, stays hidden )