Eventhandler is disabled for longer than expected - javascript

I'm making a simple full viewport scroller. You can change sections by triggering wheel event.
To prevent the eventhandler from firing many times in row and skipping pages, I've added a timer, calculating the difference between date.now() stored in variable and date.now() inside the eventHandler. This happens mostly if you spam scrolling, it makes you have to wait about 3 seconds to scroll again instead of 200ms. How to prevent this from happening?
document.ready = (fn) => {
if (document.attachEvent ? document.readyState === "complete" : document.readyState !== "loading"){
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
}
document.ready(() => {
const SECTIONS = document.querySelectorAll('.section');
let current;
let onWheelTimeout = 'poop';
let time = Date.now()
// initialize first section as active
_.first(SECTIONS).classList.add('active');
document.addEventListener('wheel', onWheel)
function goSectionUp() {
const current = document.querySelector('.active');
current.classList.remove('active');
if(current.previousElementSibling) {
current.previousElementSibling.classList.add('active');
} else {
_.last(SECTIONS).classList.add('active');
}
}
function goSectionDown() {
const current = document.querySelector('.active');
current.classList.remove('active');
if(current.nextElementSibling) {
current.nextElementSibling.classList.add('active');
} else {
_.first(SECTIONS).classList.add('active');
}
}
function onWheel(e) {
const now = Date.now()
const diff = now - time;
time = now;
if(diff > 200) {
if(e.deltaY < 0) {
onScroll('up')
} else {
onScroll('down')
}
}
}
function onScroll(direction) {
if(direction === 'up') {
goSectionUp()
} else {
goSectionDown()
}
};
});
html {
box-sizing: border-box;
overflow: hidden;
width: 100%; height: 100%;
}
*, *:before, *:after {
box-sizing: inherit;
}
body {
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;
padding: 0; margin: 0;
overflow: hidden;
height: 100%; width: 100%;
position: relative;
}
#page {
width: 100%; height: 100%;
transition: all 1s ease;
transform: none !important;
}
.section {
height: 100vh; width: 100%;
opacity: 0;
visibility: hidden;
position: absolute;
top: 0;
left: 0;
transition: all .7s ease-in-out;
}
.section:nth-of-type(1) {
background-color: red;
}
.section:nth-of-type(2) {
background-color: aquamarine;
}
.section:nth-of-type(3) {
background-color: blueviolet;
}
.section:nth-of-type(4) {}
.active {
opacity: 1; visibility: visible;
}
#button {
position: sticky;
top: 0; left: 100px;
z-index: 1000;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script>
<div id="page">
<div class="section">one</div>
<div class="section">two</div>
<div class="section">three</div>
<div class="section">four</div>
</div>

It seems like what you want is a debounce function. I'd recommend using this one, by David Walsh:
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
Usage
var myScrollFunction = debounce(function() {
// All the taxing stuff you do
}, 250);
document.addEventListener('wheel', myScrollFunction);
To answer why your code doesn't work as expected: The mouse wheel produces a series of continuous events while it is scrolling, so your time diff is constantly < 200. Here's an example of it working "properly" (though the best answer is still a true debounce function as stated above).
JSBin example
https://jsbin.com/cuqacideto/edit?html,console,output

Related

How do you show a link after scrolling 300px down a page in JS?

I am trying to make a link that's anchored to a heading appear after scrolling down 300px on my website, but my code doesn't seem to work. Does anyone know why?
NOTE-
I am using Bootstrap5 on my website.
I have altered my code based on the replies I got but I'm still facing the issue. This is how my code looks now-
Here is my code -
<a href="#header-title-1" id="customID" class="bottom-0 end-0 quick-anchor-top hide"> <i
class="fa-solid fa-arrow-up"></i></a>
.quick-anchor-top {
font-size: 25px;
padding: 15px 25px 15px 25px;
border-radius: 50px;
color: rgb(0, 0, 0);
background-color: rgba(182, 20, 20, 0.800);
transition: all 0.4s ease;
margin: 20px;
position: fixed;
z-index: 1;
}
.quick-anchor-top:hover {
transition-duration: 0.4s;
color: white;
background-color: rgba(0, 0, 0, 0.800);
}
.quick-anchor-top.show {
display: block;
}
.quick-anchor-top.hide {
display: none;
}
const myID = document.getElementById("customID");
// Reset timeout after each call
const debounce = function (func, duration = 250){
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => { func.apply(this, args); }, duration);
};
}
// Call only once per duration
function throttle(func, duration = 250) {
let shouldWait = false
return function (...args) {
if (!shouldWait) {
func.apply(this, args)
shouldWait = true
setTimeout(function () {
shouldWait = false
}, duration)
}
}
}
// Handle scroll Event
const scrollHandler = function() {
const { scrollY } = window;
if ( scrollY >= 300) {
myID.classList.add('show');
myID.classList.remove('hide');
} else {
myID.classList.add('hide');
myID.classList.remove('show');
}
};
window.addEventListener("scroll", throttle(() => scrollHandler()) );
The JavaScript code works properly: the show and hide CSS classes names are appearing. The problem is in the CSS. So, to fix it try the following:
.quick-anchor-top {
font-size: 25px;
padding: 15px 25px 15px 25px;
border-radius: 50px;
color: rgb(0, 0, 0);
background-color: rgba(182, 20, 20, 0.800);
transition: all 0.4s ease;
margin: 20px;
position: fixed;
z-index: 1;
display: none;
}
.quick-anchor-top.show {
display: block;
}
.quick-anchor-top.hide {
display: none;
}
.quick-anchor-top:hover {
transition-duration: 0.4s;
color: white;
background-color: rgba(0, 0, 0, 0.800);
}
When page just loaded
You don't need to set
class="bottom-0 end-0 quick-anchor-top hide"
change a tag to
<a href="#header-title-1" id="customID" > <i
class="fa-solid fa-arrow-up"></i></a>
change your if else to
if (y >= 300) {
myID.className = "quick-anchor-top"
} else {
myID.className = ""
}
That is not the correct way to add or remove classes. Also I would recommend using a debounce or throttle depending on how you need to handle events because a scroll event can run several hundred times in a second.
const myID = document.getElementById("customID");
// Reset timeout after each call
const debounce = function (func, duration = 250){
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => { func.apply(this, args); }, duration);
};
}
// Call only once per duration
function throttle(func, duration = 250) {
let shouldWait = false
return function (...args) {
if (!shouldWait) {
func.apply(this, args)
shouldWait = true
setTimeout(function () {
shouldWait = false
}, duration)
}
}
}
// Handle scroll Event
const scrollHandler = function() {
const { scrollY } = window;
if ( scrollY >= 300) {
myID.classList.add('show');
myID.classList.remove('hide');
} else {
myID.classList.add('hide');
myID.classList.remove('show');
}
};
window.addEventListener("scroll", throttle(() => scrollHandler()) );

How to add timer inside the progress bar

I want to add a timer which decrease Automatically (like 10 seconds, 9 seconds... till 0 seconds) but the progress bar will increase. And I am new to javascript, and the below code also copied from another site , so please help me in adding timer inside the progress bar
Till now I did this code
I want to make like this
Demo
<div class="progress"></div>
<style>
.progress-bar {
height: 20px;
background: #1da1f2;
box-shadow: 2px 14px 15px -7px rgba(30, 166, 250, 0.36);
border-radius: 50px;
transition: all 0.5s;
}
.progress {
width: 100%;
display: flex;
flex-flow: column nowrap;
justify-content: center;
align-items: start;
background: #e6e9ff;
border-radius: 20px;
box-shadow: 0px 10px 50px #abb7e9;
}
</style>
<script>
/*
* (class)Progress<nowValue, minValue, maxValue>
*/
//helper function-> return <DOMelement>
function elt(type, prop, ...childrens) {
let elem = document.createElement(type);
if (prop) Object.assign(elem, prop);
for (let child of childrens) {
if (typeof child == "string") elem.appendChild(document.createTextNode(child));
else elem.appendChild(elem);
}
return elem;
}
//Progress class
class Progress {
constructor(now, min, max, options) {
this.dom = elt("div", {
className: "progress-bar"
});
this.min = min;
this.max = max;
this.intervalCode = 0;
this.now = now;
this.syncState();
if(options.parent){
document.querySelector(options.parent).appendChild(this.dom);
}
else document.body.appendChild(this.dom)
}
syncState() {
this.dom.style.width = this.now + "%";
}
startTo(step, time) {
if (this.intervalCode !== 0) return;
this.intervalCode = setInterval(() => {
console.log("sss")
if (this.now + step > this.max) {
this.now = this.max;
this.syncState();
clearInterval(this.interval);
this.intervalCode = 0;
return;
}
this.now += step;
this.syncState()
}, time)
}
end() {
this.now = this.max;
clearInterval(this.intervalCode);
this.intervalCode = 0;
this.syncState();
}
}
let pb = new Progress(15, 0, 100, {parent : ".progress"});
//arg1 -> step length
//arg2 -> time(ms)
pb.startTo(5, 500);
//end to progress after 5s
setTimeout( () => {
pb.end()
}, 10000)
</script>
I think the core problem is that the code you copied is overly complicated especially for beginners. What I would recommend is to start from what you know and build up.
Here is the functionality you want written using only core principles of JavaScript and CSS.
let initialTime = 10; //All time in seconds
let timeLeft = initialTime;
let interval;
let progressBarTextElement = document.getElementById('progress-bar-text');
let progressBarElement = document.getElementById('progress-bar');
function render() {
let progressPercentage = (1 - (timeLeft / initialTime) ) * 100;
progressBarElement.style.width = progressPercentage + '%';
progressBarTextElement.innerHTML = timeLeft + 's';
}
function tick() {
timeLeft = timeLeft - 1;
if(timeLeft <= 0) {
clearInterval(interval); //Stops interval
}
render(); //Updates html
}
function startProgressBar() {
interval = setInterval(tick, 1000); //Will call tick every second
render();
}
startProgressBar();
html {font-family: -apple-system,BlinkMacSystemFont,"Segoe UI","Roboto","Oxygen","Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue",sans-serif;}
.progress-bar-continer {
height: 80px;
width: 100%;
position: relative;
display: flex;
justify-content: center;
align-items: center;
background-color: #406086;
}
.progress-bar {
background-color: #1b3e80;
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 0%;
transition: width 1s; /* Makes progressBar smooth */
transition-timing-function: linear; /* Try to remove this line for more tick tock feel */
}
.progress-bar-text {
color: white;
font-size: 24px;
font-weight: 700;
position: relative;
z-index: 1;
}
<div class="progress-bar-continer">
<div class="progress-bar" id="progress-bar"></div>
<div class="progress-bar-text" id="progress-bar-text"></div>
<div>
Try to understand the code best you can and you will have no problems adding any features you want on top of it. All the fancy stuff will come later with experience.

Iterate HTML classes to create class objects with javascript

class CVP {
constructor(obj) {
const { P, V, PC, BPB, MPB, VRC, VBC, FS, PROG, PROGC, TW, TC } = obj;
// player
this.player = document.querySelector(P);
// video el
this.video = document.querySelector(V);
// player controller
this.playerController = document.querySelector(PC);
// play controlers
this.bigPlayBtn = document.querySelector(BPB);
this.miniPlayBtn = document.querySelector(MPB);
// volume controllers
this.volumeRangeController = document.querySelector(VRC);
this.volumeBtnController = document.querySelector(VBC);
// fullscreen
this.fullscreen = document.querySelector(FS);
// progress bar
this.progress = document.querySelector(PROG);
this.progressCurr = document.querySelector(PROGC);
// time displayers
this.timeWhole = document.querySelector(TW);
this.timeCurr = document.querySelector(TC);
// bool vars
this.isMousedown = false;
this.isFullScreen = false;
// timer vars
this.timer = '';
this.timerTime = 1000;
}
setIsMouseDown = () => {
this.isMousedown = !this.isMousedown;
};
bigPlayBtnHandler = () => {
this.bigPlayBtn.classList.toggle('HideBigPlayBtn');
};
showHideControlls = () => {
this.player.classList.toggle('paused');
};
static changeIcon(target, remove, add) {
target.classList.remove(remove);
target.classList.add(add);
}
changePlayIcon() {
console.log(this.video.src);
const target = this.miniPlayBtn;
let icon = this.video.paused ? 'play' : 'pause';
if (icon === 'pause') {
CVP.changeIcon(target, 'play', 'pause');
} else {
CVP.changeIcon(target, 'pause', 'play');
}
}
changeVolumeIcon(volElIconID) { // not in use
const target = document.querySelector(volElIconID);
if (this.video.muted) {
CVP.changeIcon(target, 'max', 'mute');
} else {
CVP.changeIcon(target, 'mute', 'max');
}
}
handleVideoTime(time) {
// minutes and seconds So Far
let hours;
let min = Math.floor(time / 60);
let sec = Math.floor(time % 60);
let output = '';
sec = (sec < 10) ? sec = `0${sec}` : sec;
min = (min < 10) ? min = `0${min}` : min;
output = `${min}:${sec}`;
return output;
}
rangeVolumeController() {
cvp.video[this.name] = this.value;
if (cvp.volumeRangeController.value == 0) {
cvp.video.muted = true;
CVP.changeIcon(cvp.volumeBtnController, 'max', 'mute');
} else {
cvp.video.muted = false;
CVP.changeIcon(cvp.volumeBtnController, 'mute', 'max');
}
}
btnVolumeController() {
if (cvp.video.muted) {
cvp.video.muted = false;
CVP.changeIcon(cvp.volumeBtnController, 'mute', 'max');
cvp.volumeRangeController.value = 1;
cvp.video.volume = 1;
} else {
cvp.video.muted = true;
CVP.changeIcon(cvp.volumeBtnController, 'max', 'mute');
cvp.volumeRangeController.value = 0;
}
}
hideWhenFullScreen() {
if (this.isFullScreen) {
this.playerController.hidden = false; // or this.playerController.style.bottom = '0px';
document.body.style.cursor = 'auto';
clearTimeout(this.timer);
this.timer = setTimeout(() => {
this.playerController.hidden = true; // or this.playerController.style.bottom = '-50px';
document.body.style.cursor = 'none';
}, this.timerTime);
} else {
this.playerController.hidden = false; // or this.playerController.style.bottom = '0px';
document.body.style.cursor = 'auto';
}
}
toggleFullScreen() {
if (this.isFullScreen) {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitCancelFullScreen) {
document.webkitCancelFullScreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
} else {
console.error('Unable to find a fullscreen exit method.');
}
} else {
if (this.player.requestFullscreen) {
this.player.requestFullscreen();
} // standard
else if (this.player.webkitRequestFullscreen) {
this.player.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
} else if (this.player.mozRequestFullScreen) {
this.player.mozRequestFullScreen();
} else if (this.player.msRequestFullscreen) {
this.player.msRequestFullscreen();
} else {
console.error('Unable to find a fullscreen request method');
}
}
}
toggleFullScreenHelper() {
this.player.classList.toggle('isFullScreen');
this.isFullScreen = !this.isFullScreen;
}
togglePlay() {
const action = this.video.paused ? 'play' : 'pause';
this.video[action]();
this.playerController.style.bottom = '0px';
this.changePlayIcon();
this.showHideControlls();
}
showVideoTime() {
const {
currentTime,
duration
} = cvp.video;
const pct = (currentTime / duration) * 100;
cvp.progressCurr.style.width = pct + '%';
cvp.timeCurr.innerText = cvp.handleVideoTime(currentTime.toFixed(0));
cvp.timeWhole.innerText = cvp.handleVideoTime(duration);
}
scrub(e) {
console.log(e);
console.log('e', e, 'this', this);
let seconds = (e.offsetX / this.progress.offsetWidth) * this.video.duration;
this.video.currentTime = seconds;
}
loadVideo() {
// il metodo รจ da applciare assieme a un loader e ad altre gestioni degli eventi video
// esempi di loader: https://www.w3schools.com/howto/howto_css_loader.asp
// metodi video: https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/loadeddata_event
// per i tooltips: https://www.w3schools.com/howto/howto_css_tooltip.asp
console.log(this.video.src);
}
init() {
// Hook up the Event Listeners
// toggle Play
this.video.addEventListener('click', this.togglePlay.bind(this));
this.miniPlayBtn.addEventListener('click', this.togglePlay.bind(this));
this.bigPlayBtn.addEventListener('click', this.togglePlay.bind(this));
this.video.addEventListener('loadeddata', this.loadVideo.bind(this));
// bigPlayBtn show/hide handler
this.video.addEventListener('click', this.bigPlayBtnHandler);
this.bigPlayBtn.addEventListener('click', this.bigPlayBtnHandler);
this.miniPlayBtn.addEventListener('click', this.bigPlayBtnHandler);
// time update
this.video.addEventListener('timeupdate', this.showVideoTime);
// progress bar events
this.progress.addEventListener('click', this.scrub.bind(this));
this.progress.addEventListener('mousemove', (e) => this.isMousedown && this.scrub(e));
this.progress.addEventListener('mousedown', this.setIsMouseDown);
this.progress.addEventListener('mouseup', this.setIsMouseDown);
// fullscreen
this.fullscreen.addEventListener('click', this.toggleFullScreen.bind(this));
// volume controllers
this.volumeBtnController.addEventListener('click', this.btnVolumeController);
this.volumeRangeController.addEventListener('change', this.rangeVolumeController);
this.volumeRangeController.addEventListener('mousemove', this.rangeVolumeController);
// cross-browser fullscreen hanlder
const browsersFullScreen = [
'fullscreenchange',
'mozfullscreenchange',
'webkitfullscreenchange',
'msfullscreenchange',
];
browsersFullScreen.forEach(browser => document.addEventListener(browser, this.toggleFullScreenHelper.bind(this)));
// player show/hide controlls on mousemove event
this.player.addEventListener('mousemove', this.hideWhenFullScreen.bind(this));
}
}
const initializeCVP = {
P: '.CVP',
V: '.video',
PC: '.playerController',
BPB: '.bigPlayBtn',
MPB: '.miniPlayBtn',
VRC: '.volumeRC',
VBC: '.volumeBC',
FS: '.fullscreen',
PROG: '.progress',
PROGC: '.progressCurr',
TW: '.timeWhole',
TC: '.timeCurr',
};
let cvp = new CVP(initializeCVP);
cvp.init();
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
display: flex;
flex-direction: column;
justify-content: center;
align-content: center;
align-items: center;
height: 100vh;
background-image: linear-gradient(to top, #dad4ec 0%, #dad4ec 1%, #f3e7e9 100%);
/* background-image: linear-gradient(-20deg, #f794a4 0%, #fdd6bd 100%); */
}
[hidden] {
display: none !important;
}
.player {
display: flex;
justify-content: center;
border-radius: 10px;
width: 700px;
max-height: 100%;
position: relative;
font-size: 16px;
overflow: hidden;
text-align: center;
background: #000;
color: #fff;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.4), 0 10px 20px -3px rgba(0, 0, 0, 0.2);
margin-bottom: 5vh;
}
/* This css is only applied when fullscreen is active. */
.player.isFullScreen {
max-width: none;
max-height: none;
width: 100%;
height: 100%;
background: #000;
}
.player.isFullScreen video {
width: 100%;
height: auto;
}
.video {
max-width: 100%;
max-height: 100%;
-o-object-fit: fill;
object-fit: fill;
/* These are the actual dimensions of the video. Avoid the awkward element resize after the video loads. */
/* width: 640px;
height: 358px; */
}
.playerController {
display: flex;
align-items: center;
justify-content: space-evenly;
width: 98%;
max-width: 1000px;
/* limiting in fullscreen mode */
position: absolute;
background: rgba(51, 51, 51, 0.8);
color: #fff;
border-radius: 10px;
margin-bottom: 1%;
z-index: 2147483649;
transform: translateY(150px);
transition: .2s;
bottom: -50px;
/* 0 to show in preview */
}
.playerController * {
color: rgba(255, 255, 255, 0.9);
}
.playerController button {
background: none;
border: 0;
outline: 0;
text-align: center;
cursor: pointer;
}
.icon {
font-size: 16px;
font-family: 'Font Awesome 5 Free';
font-weight: 900;
}
.player:hover .playerController,
.player.paused .playerController {
transform: translateY(0);
}
.player__btn {
max-width: 50px;
}
.play::before {
content: '\f04b';
}
.pause::before {
content: '\f04c';
}
.progress {
/* position: relative; */
width: 50%;
cursor: pointer;
user-select: none;
height: 10px;
/* or 15px */
background: rgba(160, 154, 154, 0.8);
overflow: hidden;
border-radius: 50px;
}
.progressCurr {
height: 100%;
width: 0;
background: rgba(255, 255, 255, 0.9);
/* flex: 0;
flex-basis: 0%; */
/* old styles yellow progressCurr bg flex-basic insted of width*/
}
.fullscreen::before {
font-family: 'FontAwesome';
content: '\f065';
font-size: 16px
}
.player.isFullScreen .playerController .fullscreen::before {
content: '\f066';
}
.timeCurr,
.timeWhole {
font-size: 14px;
user-select: none;
}
.volumeContainer {
display: flex;
}
.volumeBC {
cursor: pointer;
padding-right: 5px;
}
.volumeRC {
width: 10px;
height: 30px;
}
.max::before {
content: '\f028'
}
.mute::before {
content: '\f6a9'
}
.bigPlayBtn {
font-size: 80px;
position: absolute;
top: 50%;
left: 50%;
cursor: pointer;
box-sizing: border-box;
transform: translate(-50%, -50%);
transition: .5s;
}
.bigPlayBtn::before {
content: '\f04b';
}
.HideBigPlayBtn {
cursor: default;
opacity: 0;
transform: translate(-50%, -50%) scale(1.2);
}
<div class='player paused CVP'>
<video class='video' src='http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4' width='700' height='350' poster='https://i.ytimg.com/vi/A1sVB8XBNDo/maxresdefault.jpg'>
</video>
<div class='bigPlayBtn icon'></div>
<div class='playerController'>
<button class='player__btn miniPlayBtn icon' title='Play/Pause'></button>
<div class='timeCurr'>00:00</div>
<div class='progress'>
<span id='buffered'>
<div class='progressCurr'></div>
</span>
</div>
<div class='timeWhole'>--:--</div>
<div class='volumeContainer'>
<button class='player__btn max volumeBC icon'></button>
<input type='range' name='volume' class='volume volumeRC' min='0' max='1' step='0.01' value='1' title='Volume' />
</div>
<button class='player__btn fullscreen icon' title='Full Screen'></button>
</div>
</div>
<div class='player paused CVP'>
<video class='video' src='http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4' width='700' height='350' poster='https://i.ytimg.com/vi/A1sVB8XBNDo/maxresdefault.jpg'>
</video>
<div class='bigPlayBtn icon'></div>
<div class='playerController'>
<button class='player__btn miniPlayBtn icon' title='Play/Pause'></button>
<div class='timeCurr'>00:00</div>
<div class='progress'>
<span id='buffered'>
<div class='progressCurr'></div>
</span>
</div>
<div class='timeWhole'>--:--</div>
<div class='volumeContainer'>
<button class='player__btn max volumeBC icon'></button>
<input type='range' name='volume' class='volume volumeRC' min='0' max='1' step='0.01' value='1' title='Volume' />
</div>
<button class='player__btn fullscreen icon' title='Full Screen'></button>
</div>
</div>
Sorry if I'm disturbing you for something that will probably appear you very simple, but I can't find a way to solve the problem on my own.
And, maybe, the title is not appropriated...
Well, I have a class with a constructor that works with HTML tag VIDEO, named CVP. It creates methods to act on video controls. The initial method consist in hooking the event listeners to HTML controls'classes.
Anyway, with a single VIDEO tag in the HTML code, it works correctly, but I don't know how to iterate the class on any VIDEO when there are many of them. This is an examples of the VIDEO tag:
<div class='player paused CVP'>
<video class='video' src="../../video/pietro.mp4">
</video>
etc...
This is the beginnig of the JS CLASS:
class CVP {
constructor (obj) {
const { P, V, PC, BPB, MPB, VRC, VBC, FS, PROG, PROGC, TW, TC } = obj;
this.player = document.querySelector(P);
this.video = document.querySelector(V);
etc..
The class ends this way:
(many other methods...)
init () {
// Hook up the Event Listeners
// toggle Play
this.video.addEventListener('click', this.togglePlay.bind(this));
this.miniPlayBtn.addEventListener('click', this.togglePlay.bind(this));
this.bigPlayBtn.addEventListener('click', this.togglePlay.bind(this));
etc...
}
}
Then, there's a inizialization's variable:
const initializeCVP = {
P: '.CVP',
V: '.video',
PC: '.playerController',
etc...
};
To add a single VIDEO tag:
const cvp = new CVP(initializeCVP);
cvp.init();
In this case it works perfectly. But, How can I assign the JS class to many VIDEO tags?
I tried this way, but it doesn't work:
window.onload = function () {
const CVPs = document.querySelectorAll('.CVP');
CVPs.forEach((cvp) => {
cvp = new CVP(initializeCVP);
cvp.init();
});
};
Is it clear? I hope so. I'm quite a beginner, so plase, don't complain...
Change your constructor so it takes elements rather than selectors. The caller can call document.querySelector() or document.querySelectorAll() when creating the initialization object.
class CVP {
constructor (obj) {
const { P, V, PC, BPB, MPB, VRC, VBC, FS, PROG, PROGC, TW, TC } = obj;
this.player = P;
this.video = V;
etc..
}
}
window.onload = function() {
const players = document.querySelectorAll('.player');
players.forEach(player => {
const initializeCVP = {
P: player,
V: player.querySelector('.video'),
PC: player.querySelector('.playerController'),
BPB: player.querySelector('.bigPlayBtn'),
MPB: player.querySelector('.miniPlayBtn'),
VRC: player.querySelector('.volumeRC'),
VBC: player.querySelector('.volumeBC'),
FS: player.querySelector('.fullscreen'),
PROG: player.querySelector('.progress'),
PROGC: player.querySelector('.progressCurr'),
TW: player.querySelector('.timeWhole'),
TC: player.querySelector('.timeCurr'),
};
const cvp = new CVP(initializeCVP);
cvp.init();
});
};
I found a way to fix what #Barman and #RandyCasburn pointed out regarding the single 'cvp' istance connected to class methods.
First, I applied #Barman suggestion, so to iterate all players has in the previous answer.
Then, I put a global array to collect the single 'cvp', with a couple of other new variables:
let cvpItems = [];
let cvpItem;
let currentCVP;
Then, I pushed all player istances in the HTML, applying to anyone an addEventListener. The window.onload code is changed in this way:
window.onload = function () {
const players = document.querySelectorAll('.player');
players.forEach(player => {
const initializeCVP = {
P: player,
V: player.querySelector('.video'),
PC: player.querySelector('.playerController'),
BPB: player.querySelector('.bigPlayBtn'),
MPB: player.querySelector('.miniPlayBtn'),
VRC: player.querySelector('.volumeRC'),
VBC: player.querySelector('.volumeBC'),
FS: player.querySelector('.fullscreen'),
PROG: player.querySelector('.progress'),
PROGC: player.querySelector('.progressCurr'),
TW: player.querySelector('.timeWhole'),
TC: player.querySelector('.timeCurr'),
};
cvpItem = new CVP(initializeCVP);
cvpItems.push(cvpItem);
cvpItem.init();
player.addEventListener('click', currentItem, false);
});
};
With the currentItem function I find the current player iteration using the title attribute in the HTML video tags:
let currentItem = function () {
for (var i = 0; i < cvpItems.length; i++) {
if (cvpItems[i].video.title == this.querySelectorAll('.video')[0].title) {
currentCVP = i;
break;
}
}
};
So, now I can identify the specific player in the JS class. For examples, the previous rangeVolumeController method changes from:
rangeVolumeController () {
cvp.video[this.name] = this.value;
if (cvp.volumeRangeController.value == 0) {
cvp.video.muted = true;
CVP.changeIcon(cvp.volumeBtnController, 'max', 'mute');
} else {
cvp.video.muted = false;
CVP.changeIcon(cvp.volumeBtnController, 'mute', 'max');
}
}
to
rangeVolumeController () {
console.log(CVP, this, event.target);
cvpItems[currentCVP].video[this.name] = this.value;
if (cvpItems[currentCVP].volumeRangeController.value == 0) {
cvpItems[currentCVP].video.muted = true;
CVP.changeIcon(cvpItems[currentCVP].volumeBtnController, 'max', 'mute');
} else {
cvpItems[currentCVP].video.muted = false;
CVP.changeIcon(cvpItems[currentCVP].volumeBtnController, 'mute', 'max');
}
}
It works! But, I know, it's not an elegant way for coding. If someone wants to propose a better or more "elegant" solution, I'll be grateful.

How to change navigation text color on fullscreen scroll based on background?

Title says it all really. Here is an example of what I'm trying to achieve navigation color change image based on background
The problem is that it needs to work with a site that's using a scroll-jacking parallax type effect, here is the site I'm trying to achieve this effect with demo website
Modify the scroll script a bit
Check demo here
Created the function toggleHeaderColor to check the current section. Since the scroll script is indexing each section in order 0 (i.e. section_1) ,1 (i.e. section_2),2 (i.e. section_2),3 (i.e. section_3),4 (i.e. section_2) and so on. Every time you scroll it gets updated.
In scroll script there are two function nextItem() and previousItem()form which we get the current slide index and on that we can call our function to toggle dark class on header elements.
JS:
var sectionBlock = $(".section-item");
var getCurrentSlideAttr = 0;
function toggleHeaderColor(getCurrentSlideAttr) {
if (getCurrentSlideAttr == 0) {
$(".menu-link, .menu-link-logo, .menu-link-last").removeClass("dark");
}
if (getCurrentSlideAttr == 1) {
$(".menu-link, .menu-link-logo, .menu-link-last").addClass("dark");
}
if (getCurrentSlideAttr == 2) {
$(".menu-link, .menu-link-logo, .menu-link-last").removeClass("dark");
}
if (getCurrentSlideAttr == 3) {
$(".menu-link, .menu-link-logo, .menu-link-last").removeClass("dark");
}
if (getCurrentSlideAttr == 4) {
$(".menu-link, .menu-link-logo, .menu-link-last").addClass("dark");
}
}
var ticking = false;
var isFirefox = /Firefox/i.test(navigator.userAgent);
var isIe =
/MSIE/i.test(navigator.userAgent) ||
/Trident.*rv\:11\./i.test(navigator.userAgent);
var scrollSensitivitySetting = 30;
var slideDurationSetting = 800;
var currentSlideNumber = 0;
var totalSlideNumber = $(".section-item").length;
function parallaxScroll(evt) {
if (isFirefox) {
delta = evt.detail * -120;
} else if (isIe) {
delta = -evt.deltaY;
} else {
delta = evt.wheelDelta;
}
if (ticking != true) {
if (delta <= -scrollSensitivitySetting) {
ticking = true;
if (currentSlideNumber !== totalSlideNumber - 1) {
currentSlideNumber++;
nextItem();
}
slideDurationTimeout(slideDurationSetting);
}
if (delta >= scrollSensitivitySetting) {
ticking = true;
if (currentSlideNumber !== 0) {
currentSlideNumber--;
}
previousItem();
slideDurationTimeout(slideDurationSetting);
}
}
}
function slideDurationTimeout(slideDuration) {
setTimeout(function() {
ticking = false;
}, slideDuration);
}
var mousewheelEvent = isFirefox ? "DOMMouseScroll" : "wheel";
window.addEventListener(mousewheelEvent, _.throttle(parallaxScroll, 60), false);
function nextItem() {
getCurrentSlideAttr = currentSlideNumber;
toggleHeaderColor(getCurrentSlideAttr);
var $previousSlide = $(".section-item").eq(currentSlideNumber - 1);
$previousSlide
.css("transform", "translate3d(0,-130vh,0)")
.find(".content-wrapper")
.css("transform", "translateY(40vh)");
currentSlideTransition();
}
function previousItem() {
//console.log($(".section-item").eq(currentSlideNumber).attr('id'))
getCurrentSlideAttr = currentSlideNumber;
toggleHeaderColor(getCurrentSlideAttr);
var $previousSlide = $(".section-item").eq(currentSlideNumber + 1);
$previousSlide
.css("transform", "translate3d(0,30vh,0)")
.find(".content-wrapper")
.css("transform", "translateY(30vh)");
currentSlideTransition();
}
function currentSlideTransition() {
var $currentSlide = $(".section-item").eq(currentSlideNumber);
$currentSlide
.css("transform", "translate3d(0,-15vh,0)")
.find(".content-wrapper")
.css("transform", "translateY(15vh)");
}
Update
You can actually choose a specific text color over white/black backgrounds using css blend modes.
Example with specific colors (green over white and red over black in this case):
html, body {
margin: 0;
}
h1 {
position: fixed; top: 0; left: 0;
width: 100vw;
text-align: center;
mix-blend-mode: difference;
color: white;
z-index: 1;
}
div {
position: relative;
width: 100vw;
height: 100vh;
background: white;
margin: 0;
}
div:nth-of-type(2n) {
background: black;
}
div:after {
content: '';
position: absolute; top: 0; left: 0;
width: 100%;
height: 100%;
z-index: 2;
}
div:nth-of-type(2n):after {
background: red;
mix-blend-mode: multiply;
}
div:nth-of-type(2n + 1):after {
background: green;
mix-blend-mode: screen;
}
<h1>Scroll to see effect</h1>
<div></div>
<div></div>
<div></div>
I think the only way you would be able to choose the exact partial colors using SVG text or paths.
A simple example with mix-blend-mode:
html, body {
margin: 0;
}
h1 {
position: fixed; top: 0; left: 0;
width: 100vw;
text-align: center;
mix-blend-mode: difference;
color: white;
z-index: 1;
}
div {
width: 100vw;
height: 100vh;
background: black;
}
div:nth-of-type(2n) {
background: white;
}
<h1>Scroll to see effect</h1>
<div></div>
<div></div>
<div></div>
Browser support
https://css-tricks.com/reverse-text-color-mix-blend-mode/
Try Adding mix-blend-mode property.
Add this property to your .navigation-menu class
CSS
.navigation-menu{
mix-blend-mode: difference;
}
Hope this Helps...

Check when element appears in the viewport -> addClass

I have a lot of objects in the dom tree, on which i'm adding new class, when they appeat in the viewport. But my code is very slow - it causes page to slow down...
I have such dom:
...
<span class="animation"></span>
...
and such jquery:
$.each($('.animation'), function() {
$(this).data('offset-top', Math.round($(this).offset().top));
});
var wH = $(window).height();
$(window).on('scroll resize load touchmove', function () {
var windowScroll = $(this).scrollTop();
$.each($('.animation'), function() {
if (windowScroll > (($(this).data('offset-top') + 200) - wH)){
$(this).addClass('isShownClass');
}
});
});
maybe i can somehow speed up my scroll checking and class applying?
You can use the Intersection Observer API to detect when an element appears in the viewport. Here is an example that adds a class to an element that is scrolled into the viewport and animates the background color from red to blue:
var targetElement = document.querySelector('.block');
var observer = new IntersectionObserver(onChange);
observer.observe(targetElement);
function onChange(entries) {
entries.forEach(function (entry) {
entry.target.classList.add('in-viewport');
observer.unobserve(entry.target);
});
}
body {
margin: 0;
height: 9000px;
}
.block {
width: 100%;
height: 200px;
margin-top: 2000px;
background-color: red;
transition: background 1s linear;
}
.block.in-viewport {
background-color: blue;
}
<div class="block">
</div>
The Intersection Observer API method works on chrome only, but the performance faster by 100%. The code below loads in 3/1000 second
$(document).ready(function () {
'use strict';
var startTime, endTime, sum;
startTime = Date.now();
var anim = $('.animation');
anim.each(function (index, elem) {
var animoffset = $(elem).offset().top;
$(window).on('scroll resize touchmove', function() {
var winScTop = $(this).scrollTop();
var windowHeight = $(window).height();
var winBottom = winScTop + windowHeight;
if ( winBottom >= animoffset ) {
$(elem).addClass('showed');
}
});
});
endTime = Date.now();
sum = endTime - startTime;
console.log('loaded in: '+sum);
});
html {
height: 100%;
}
body {
margin: 0;
height: 9000px;
}
.animation {
display: block;
width: 400px;
height: 400px;
background-color: blue;
margin-top: 1000px;
}
.animation:not(:first-of-type) {
margin-top: 10px;
}
.animation.showed {
background-color: yellow;
transition: all 3s ease
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
<span class="animation"></span>
<span class="animation"></span>
<span class="animation"></span>
<span class="animation"></span>
IntersectionObserver has a limited support in browsers, but it's improving.
I'm basically lazy loading the polyfill only if the browser user is loading my website in doesn't support IntersectionObserver API with the code bellow.
loadPolyfills()
.then(() => /* Render React application now that your Polyfills are
ready */)
/**
* Do feature detection, to figure out which polyfills needs to be imported.
**/
function loadPolyfills() {
const polyfills = []
if (!supportsIntersectionObserver()) {
polyfills.push(import('intersection-observer'))
}
return Promise.all(polyfills)
}
function supportsIntersectionObserver() {
return (
'IntersectionObserver' in global &&
'IntersectionObserverEntry' in global &&
'intersectionRatio' in IntersectionObserverEntry.prototype
)
}

Categories

Resources