Scroll progress bar between items on a page - javascript

I have a page with progress, that should show progress between items, and when you arrive at the item, the bubble turns yellow. The bubble part works fine with getBoundingClientRect, however the scroll calculation is wrong (screenshot):
Code:
scollbar HTML (blade):
<div class="container grid grid-cols-12">
<div class='col-span-3 w-full text-white flex bg-cold85 pt-20 pl-11 pb-28 h-full sticky top-[90px] max-h-fit' >
<div class='flex shrink-0 items-center'>
<div class="w-[1px] h-full mt-[12px] -mr-[1px] z-30">
<div class="line w-[1px] h-[0%] !z-30 relative bg-yellow scroll-line"></div>
{{-- <div class="line w-[1px] h-[calc(100%_-_20px)] relative bg-cream"></div> --}}
</div>
<div class="w-[1px] h-full mt-[12px] -mr-[6px] z-0">
{{-- <div class="line w-[1px] h-[50%] !z-30 relative bg-yellow"></div> --}}
<div class="line w-[1px] h-[calc(100%_-_20px)] relative bg-cream"></div>
</div>
<div class="scrollcontainer">
#foreach ($items as $item)
<div class="flex items-center gap-x-6 mb-13 last:mb-0 children:first:border-yellow children:first:bg-yellow scrollbar-item" id='{{$item['id']}}'>
<i class="z-10 w-[11px] h-[11px] rounded-full border-2 border-cream bg-cold">
</i>
{{$item['text']}}
</div>
#endforeach
</div>
</div>
</div>
<div class="col-span-9 scroll-part">
#php(the_content())
</div>
</div>
script:
const items = document.querySelectorAll('.scrollbar-selector');
const scrollContainer = document.querySelector('.scrollcontainer');
window.addEventListener('scroll', () => {
const scroll = window.scrollY;
// const doc = document.body.clientHeight;
// const win = window.innerHeight;
const scrollPart = document.querySelector('.scroll-part').clientHeight;
let value = (scroll / (scrollPart)) * 100;
items.forEach(i => {
if (i.getBoundingClientRect().top <= 500) {
let id = i.getAttribute('id');
let sItem = scrollContainer.querySelector(`#${id} i`);
sItem.style.borderColor = '#F4C514';
sItem.style.backgroundColor = '#F4C514';
} else {
let id = i.getAttribute('id');
let sItem = scrollContainer.querySelector(`#${id} i`);
sItem.style.backgroundColor = '#151616';
sItem.style.borderColor = 'white';
}
});
const line = document.querySelector('.scroll-line');
line.style.height = `${value}%`;
});

Related

How do I make this caroussel slider mouse / touch draggable (Tailwind CSS and Alpine JS)

I am trying to build a website and have this carousel, that was made with Alpine JS and Tailwind CSS. I took the Alpine Javascript from a template and it works according to specifications. But I would want to make it mouse draggable as well.
Here is the carousel slider
<div class="mt-24">
<script>
window.carousel = function () {
return {
container: null,
prev: null,
next: null,
init() {
this.container = this.$refs.container
this.update();
this.container.addEventListener('scroll', this.update.bind(this), {passive: true});
},
update() {
const rect = this.container.getBoundingClientRect();
const visibleElements = Array.from(this.container.children).filter((child) => {
const childRect = child.getBoundingClientRect()
return childRect.left >= rect.left && childRect.right <= rect.right;
});
if (visibleElements.length > 0) {
this.prev = this.getPrevElement(visibleElements);
this.next = this.getNextElement(visibleElements);
}
},
getPrevElement(list) {
const sibling = list[0].previousElementSibling;
if (sibling instanceof HTMLElement) {
return sibling;
}
return null;
},
getNextElement(list) {
const sibling = list[list.length - 1].nextElementSibling;
if (sibling instanceof HTMLElement) {
return sibling;
}
return null;
},
scrollTo(element) {
const current = this.container;
if (!current || !element) return;
const nextScrollPosition =
element.offsetLeft +
element.getBoundingClientRect().width / 2 -
current.getBoundingClientRect().width / 2;
current.scroll({
left: nextScrollPosition,
behavior: 'smooth',
});
}
};
}
</script>
<style>
.scroll-snap-x {
scroll-snap-type: x mandatory;
}
.snap-center {
scroll-snap-align: center;
}
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
</style>
<div class="flex mx-auto items-center">
<div x-data="carousel()" x-init="init()" class="relative overflow-hidden group">
<div x-ref="container"
class="ml-4 flex overflow-x-scroll scroll-snap-x space-x-4 no-scrollbar touch-pan-x cursor-pointer">
{% for origin in origins %}
<div class="group/item relative ml-4 flex-auto flex-grow-0 flex-shrink-0 w-4/5 sm:w-4/5 xl:w-2/5 rounded-lg bg-gray-100 items-center justify-center snap-center overflow-hidden shadow-md"><!-- items container -->
<div class="min-w-full h-full rounded-lg bg-gray-100 overflow-hidden shadow-md">
<div><img src="{{ origin.imgurl }}" alt="{{ origin.alt }}" class="object-cover h-96" /></div>
<div class="absolute bg-gray-800 bg-opacity-0 group-hover/item:bg-opacity-50 top-2/3 inset-x-0 text-center px-2 py-3">
<div class="text-2xl text-transparent group-hover/item:text-emerald-500 font-extrabold uppercase">{{ origin.name }}</div>
<div class="text-xl text-transparent group-hover/item:text-white font-bold">{{ origin.country }}</div>
</div>
</div>
</div>
{% endfor %}
</div>
<div #click="scrollTo(prev)" x-show="prev !== null"
class="block absolute top-1/2 left-0 bg-white text-3xl p-2 rounded-full transition-transform ease-in-out transform -translate-x-full -translate-y-1/2 group-hover:translate-x-0 cursor-pointer">
<div><ion-icon name="chevron-back-outline"></ion-icon></div>
</div>
<div #click="scrollTo(next)" x-show="next !== null"
class="block absolute top-1/2 right-0 bg-white p-2 text-3xl rounded-full transition-transform ease-in-out transform translate-x-full -translate-y-1/2 group-hover:translate-x-0 cursor-pointer">
<div><ion-icon name="chevron-forward-outline"></ion-icon></div>
</div>
</div>
</div>
</div>
</div>
I tried adding Alpine script from a different carousel that has this function, but it does not do anything. My understanding of Javascript is rudimentary.

This code only allows the use of Carousel Post style one time

I recently learned about web development, and I want to build my own portfolio website using Github Pages. For the 'Portfolio' section, I want to use a slider using Javascript. Then, when I copy paste the code and change the title to 'Blog' section, this new section didn't apply the slider that seen in Portfolio.
I already seen multiple online sources to see where is my problem, and it shows that the getElementById only can be used one time. I try to modify the code to use querySelectorAll but it's still doesn't work.
This is the JS code that I used:
const sliderContainer = document.getElementById('sliderContainer');
const slider = document.getElementById('slider');
const cards = slider.getElementsByTagName('li');
var elementsToShow = 3;
if (document.body.clientWidth < 1000) {
elementsToShow = 1;
} else if (document.body.clientWidth < 1500) {
elementsToShow = 2;
}
var sliderContainerWidth = sliderContainer.clientWidth;
var cardWidth = sliderContainerWidth / elementsToShow;
slider.style.width = cards.length * cardWidth + 'px';
slider.style.transition = 'margin';
slider.style.transitionDuration = '1s';
for (var index = 0; index < cards.length; index++) {
const element = cards[index];
element.style.width = cardWidth + 'px';
}
function next() {
if (+slider.style.marginLeft.slice(0, -2) != -cardWidth * (cards.length - elementsToShow)) slider.style.marginLeft = +slider.style.marginLeft.slice(0, -2) - cardWidth + 'px';
}
function prev() {
if (+slider.style.marginLeft.slice(0, -2) != 0) slider.style.marginLeft = +slider.style.marginLeft.slice(0, -2) + cardWidth + 'px';
}
function autoPlay() {
prev();
if (+slider.style.marginLeft.slice(0, -2) === -cardWidth * (cards.length - elementsToShow)) {
slider.style.marginLeft = '0px';
}
setTimeout(() => {
autoPlay();
}, 3000);
}
and this is my HTML code. I use TailwindCSS for the CSS framework.
<div class="flex">
<div class="flex items-center">
<div class="w-full text-right">
<button class="bi bi-arrow-left-circle-fill mb-2 ml-2 flex h-9 w-9 items-center justify-center rounded-full border border-slate-300 text-slate-300 hover:border-primary hover:bg-primary hover:text-white"
onclick="prev()"
></button>
</div>
</div>
<div id="sliderContainer" class="overflow-hidden">
<ul id="slider" class="flex w-full">
<li>Slide 1</li>
<li>Slide 2</li>
<li>Slide 3</li>
</ul>
</div>
<div class="flex items-center">
<div class="w-full">
<button
onclick="next()"
class="bi bi-arrow-right-circle-fill mr-2 mb-2 ml-2 flex h-9 w-9 items-center justify-center rounded-full border border-slate-300 text-slate-300 hover:border-primary hover:bg-primary hover:text-white slider__btn--right"
></button>
</div>
</div>
</div>
Please help me modify the JS code so it can be use multiple times. thanks in advance!

jspdf: horizontally centered elements show differently in final pdf

I am using tailwindcss and flexboxes to create the following elements which are perfectly aligned horizontally:
<div className="flex flex-row justify-between">
<div className="text-body-light text-lg">Top 500</div>
{/** Percentage */}
{this.state.top_500_progress >= 0 ?
<div className="flex flex-row items-center bg-report-green-progress-background gap-x-1 px-1">
<ProgressUpIcon />
<div className="text-report-green-progress text-lg">{this.state.top_500_progress.toFixed()}%</div>
</div>
: <div className="flex flex-row items-center bg-report-red-progress-background gap-x-1 px-1">
<ProgressDownIcon />
<div className="text-report-red-progress text-lg">{this.state.top_500_progress.toFixed()}%</div>
</div>
}
</div>
Then I am exporting my page as pdf using jspdf this way:
generatePDF = () => {
var chartEl = document.getElementById("page2")
let input: any = window.document.getElementsByClassName("page2")[0]
html2canvas(input).then(canvas => {
const img = canvas.toDataURL("image/jpeg", 1.0);
var doc = new jsPDF('landscape', 'pt', [842, 455]);
doc.addImage(img, 'JPEG', 0, 0, 850, 455 );
doc.save("chart.pdf");
});
}
However my text is not aligned anymore:
I have tried adding a div with my-auto for each text element but still same result.
How to make it aligned in jspdf?

Finding the comma, where is it coming from? Javascript Arrays within Objects [duplicate]

This question already has answers here:
Unexpected comma using map()
(4 answers)
Closed last year.
Ok so i have hacked something together it works as intended but I get a comma between entries more than one.
I have this
chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) {
let markup = '';
markup = `
<section>
<div class="inner-element">
<div class="question flex justify-between px-6 py-4 bg-purple-800">
<span class="text-base text-white font-bold">HCP365 Pixels</span>
<button><i class="fas fa-plus-circle"></i></button>
</div>
<div class="answer hideText">
${HCP365Object.sitePixels.map((x) => x)}
${HCP365Object.searchPixels.map((x) => x)}
${HCP365Object.emailPixels.map((x) => x)}
${HCP365Object.programmaticPixels.map((x) => x)}
</div>
</div>
<div class="inner-element">
<div class="question flex justify-between px-6 py-4 bg-gray-500">
<span class="text-base text-white font-bold">NON HCP365 Pixels</span>
<button><i class="fas fa-plus-circle"></i></button>
</div>
<div class="answer hideText">
${HCP365Object.nonHCP365Pixels.map((x) => x)}
</div>
</div>
`;
markup += '</section>';
sendResponse(markup);
});
Which in turn gives me this:
Where the little bugger is here:
Between the element which is :
const pixelInfo = `
<div class="element">
<div class="question flex justify-between px-6 py-4 ${colorGrade}">
<span class="text-base text-white font-bold">${pixelType}</span>
<button><i class="fas fa-plus-circle"></i></button>
</div>
<div class="answer hideText">
<span id="pixel-url" class="c-word-wrap text-sm font-mono">${pixelUrl}</span>
<span id="query-params">${queryParams}</span>
</div>
</div>
`;
Each pixelInfo is pushed into it's own array which is within an object. Is this comma here because I am mapping through an array and it's chucking out the , ? Is there a way to get rid of it?
This is my whole code
const HCP365Object = {
sitePixels: [],
searchPixels: [],
emailPixels: [],
programmaticPixels: [],
nonHCP365Pixels: [],
};
// Function to break down the http request into our pixel url with a subset of associating query params.
const onBeforeSendHeadersListener = function (details) {
let regex = /[?&]([^=#]+)=([^&#]*)/g,
pixelType,
pixelUrl = `${details.url}`,
queryParams,
params = {},
match,
colorGrade;
if (details.url.includes('&ch=1&')) {
pixelType = 'HCP365 Site Pixel';
colorGrade = 'bg-purple-600';
} else if (details.url.includes('&ch=2&')) {
pixelType = 'HCP365 Search Pixel';
colorGrade = 'bg-purple-500';
} else if (details.url.includes('&ch=3&')) {
pixelType = 'HCP365 Email Pixel';
colorGrade = 'bg-purple-400';
} else if (details.url.includes('&ch=4&')) {
pixelType = 'HCP365 Programmatic Pixel';
colorGrade = 'bg-purple-300';
} else {
pixelType = 'Non HCP365 PP Pixel';
colorGrade = 'bg-gray-400';
}
// Splitting url's query params out to key value pairs
while ((match = regex.exec(details.url))) {
params[match[1]] = match[2];
}
// Looping through object's key value pair to place into divs
for (const [key, value] of Object.entries(params)) {
queryParams += `
<div class="text-sm my-1">
<span class="font-bold uppercase mr-1">${key}: </span>
<span class="font-normal font-mono capitalize c-word-wrap">${value}</span>
</div>
`;
}
const pixelInfo = `
<div class="element">
<div class="question flex justify-between px-6 py-4 ${colorGrade}">
<span class="text-base text-white font-bold">${pixelType}</span>
<button><i class="fas fa-plus-circle"></i></button>
</div>
<div class="answer hideText">
<span id="pixel-url" class="c-word-wrap text-sm font-mono">${pixelUrl}</span>
<span id="query-params">${queryParams}</span>
</div>
</div>
`;
// push the relevant pixel into the correct array
if (details.url.includes('&ch=1&')) {
HCP365Object.sitePixels.push(pixelInfo);
} else if (details.url.includes('&ch=2&')) {
HCP365Object.searchPixels.push(pixelInfo);
} else if (details.url.includes('&ch=3&')) {
HCP365Object.emailPixels.push(pixelInfo);
} else if (details.url.includes('&ch=4&')) {
HCP365Object.programmaticPixels.push(pixelInfo);
} else {
HCP365Object.nonHCP365Pixels.push(pixelInfo);
}
return HCP365Object;
};
// Apply the function entered to each header coming from contextweb domain
chrome.webRequest.onBeforeSendHeaders.addListener(
onBeforeSendHeadersListener,
{
urls: ['https://*.contextweb.com/bh/*'],
},
['requestHeaders']
);
// Initiate connection to send Message over to popup.js
chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) {
let markup = '';
markup = `
<section>
<div class="inner-element">
<div class="question flex justify-between px-6 py-4 bg-purple-800">
<span class="text-base text-white font-bold">HCP365 Pixels</span>
<button><i class="fas fa-plus-circle"></i></button>
</div>
<div class="answer hideText">
${HCP365Object.sitePixels.map((x) => x)}
${HCP365Object.searchPixels.map((x) => x)}
${HCP365Object.emailPixels.map((x) => x)}
${HCP365Object.programmaticPixels.map((x) => x)}
</div>
</div>
<div class="inner-element">
<div class="question flex justify-between px-6 py-4 bg-gray-500">
<span class="text-base text-white font-bold">NON HCP365 Pixels</span>
<button><i class="fas fa-plus-circle"></i></button>
</div>
<div class="answer hideText">
${HCP365Object.nonHCP365Pixels.map((x) => x)}
</div>
</div>
`;
markup += '</section>';
sendResponse(markup);
});
I know it's very hacky! But it works
Taking another look at
markup = `
...
<div class="answer hideText">
${HCP365Object.nonHCP365Pixels.map((x) => x)}
</div>
...
`;
The problem is HCP365Object.nonHCP365Pixels.map((x) => x). map() returns an array, and calling toString() will list all items separated by commas.
You will need to join() these to build the content without the commas.
Also, since .map((x) => x) just returns a copy of the array you can get rid of that too.
markup = `
...
<div class="answer hideText">
${HCP365Object.nonHCP365Pixels.join('')}
</div>
...
`;
You should use .join('') everywhere you use .map((x) => x)

jQuery animate number function. Want to turn it into Vanilla JavaScript

this function animates number inside an element to a defined number inside data-count value
How could I please do it in vanilla JavaScript
<div class="counter">
<div class="row no-gutters">
<div class="col-4">
<div
class="single-counter counter-color-1 d-flex align-items-center justify-content-center"
>
<div class="counter-items text-center">
<span id="count" data-count="175">0</span
><span>K</span>
<p>Downloads</p>
</div>
</div>
</div>
<div class="col-4">
<div
class="single-counter counter-color-2 d-flex align-items-center justify-content-center"
>
<div class="counter-items text-center">
<span id="count" data-count="73">0</span
><span>K</span>
<p>Active users</p>
</div>
</div>
</div>
<div class="col-4">
<div
class="single-counter counter-color-3 d-flex align-items-center justify-content-center"
>
<div class="counter-items text-center">
<span id="count" data-count="4.8">0</span>
<p>user rating</p>
</div>
</div>
</div>
</div>
$('#count').each(function() {
var counter = $(this),
countTo = counter.attr('data-count');
const countObj = { countNum: counter.text()}
$(countObj).animate({
countNum: countTo
},{
duration: 2000,
easing:'linear',
step: function() {
counter.text(Math.floor(this.countNum));
},
complete: function() {
counter.text(this.countNum);
}
});
});
I tried this
countUp(elem) {
var current = elem.innerHTML;
var interval = setInterval(increase, 70);
function increase() {
elem.innerHTML = current++;
if (current > elem.getAttribute("data-count")) {
clearInterval(interval);
}
}
}
var span = document.querySelectorAll("#count");
var i = 0;
for (i; i < span.length; i++) {
countUp(span[i]);
}
but it doesn't finish all elements animation at the same time; the elements which has the lower data-count value finishes earlier than the others that have higher data-count value
element is not a selector in your case. Another issue is all <span> have duplicate id i.e. count
I have modified these duplicate ids to count1, count2, count3. And selector $('span[id^=count') in script below means all <span> elements which have id starting with word count
$('span[id^=count').each(function() {
var counter = $(this),
countTo = counter.attr('data-count');
const countObj = { countNum: counter.text()}
$(countObj).animate({
countNum: countTo
},
{
duration: 2000,
easing:'linear',
step: function() {
counter.text(Math.floor(this.countNum));
},
complete: function() {
counter.text(this.countNum);
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<div class="counter">
<div class="row no-gutters">
<div class="col-4">
<div class="single-counter counter-color-1 d-flex align-items-center justify-content-center">
<div class="counter-items text-center">
<span id="count1" data-count="175">0</span><span>K</span>
<p>Downloads</p>
</div>
</div>
</div>
<div class="col-4">
<div class="single-counter counter-color-2 d-flex align-items-center justify-content-center">
<div class="counter-items text-center">
<span id="count2" data-count="73">0</span><span>K</span>
<p>Active users</p>
</div>
</div>
</div>
<div class="col-4">
<div class="single-counter counter-color-3 d-flex align-items-center justify-content-center">
<div class="counter-items text-center">
<span id="count3" data-count="4.8">0</span>
<p>user rating</p>
</div>
</div>
</div>
</div>
EDIT : Below is pure vanilla js function for you
You just need some basic maths to decide time interval for all elements
function countUp(elem) {
var current = elem.innerHTML;
// assume 2000(milliseconds) is time delay to complete all animations
// determine time interval based on value of data-count
var timeIntervalBeforeIncrement = 2000/elem.getAttribute("data-count")
var interval = setInterval(increase, timeIntervalBeforeIncrement);
function increase() {
elem.innerHTML = current++;
if (current > elem.getAttribute("data-count")) {
clearInterval(interval);
}
}
}
var span = document.querySelectorAll("[id^='count']");
for (i = 0; i < span.length; i++) {
countUp(span[i]);
}

Categories