how to allow scroll on mobile using touchscreen passive handler - javascript

i'm desining a horizzontal scrolling website, but i want that feature only in Desktop mode and i would like to let standard scrolling in tablets and mobile devices.
right now this is my setup:
<script>
document.addEventListener('touchstart', handler, {passive: true});
addEventListener(document, "touchstart", function(e) {
console.log(e.defaultPrevented); // will be false
e.preventDefault(); // does nothing since the listener is passive
console.log(e.defaultPrevented); // still false
});
const SCROLL_SPEED = 70;
requestAnimationFrame(function scroll() {
const nextScrollX = sectionAnchorPointer[nextSectionIndex];
// linear animtion
// if (Math.abs(window.scrollX - nextScrollX) > SCROLL_SPEED) {
// const val =
// -Math.abs(window.scrollX - nextScrollX) / (window.scrollX - nextScrollX);
// window.scroll(window.scrollX + val * SCROLL_SPEED, window.scrollY);
// } else {
// window.scroll(nextScrollX, window.scrollY);
// }
// curve animation
if (Math.abs(window.scrollX - nextScrollX) > 1) {
let val = (nextScrollX - window.scrollX) / 8;
val = val > 0 ? Math.max(val, 1) : Math.min(val, -1);
window.scroll(window.scrollX + val, window.scrollY);
} else {
window.scroll(nextScrollX, window.scrollY);
}
requestAnimationFrame(scroll);
});
let sectionAnchorPointer = [];
const resizeHandler = () => {
const content1 = document.getElementById("content1");
const content2 = document.getElementById("content2");
const content3 = document.getElementById("content3");
const content4 = document.getElementById("content4");
sectionAnchorPointer = [
content1.offsetLeft,
content2.offsetLeft,
content3.offsetLeft,
content4.offsetLeft
];
};
window.addEventListener("resize", resizeHandler);
let nextSectionIndex = 0;
const getCurrentSectionIndex = () =>
sectionAnchorPointer.findIndex((leftValue, i, array) => {
const scrollX = Math.ceil(window.scrollX); // Fixed a bug where scrollX was decimalized
const rightValue = array[i + 1] ?? Infinity;
return leftValue <= scrollX && scrollX < rightValue;
});
window.addEventListener("wheel", ({ deltaY }) => {
const currentSectionIndex = getCurrentSectionIndex();
const add = Math.abs(deltaY) / deltaY;
nextSectionIndex = currentSectionIndex + add;
nextSectionIndex = Math.min(
sectionAnchorPointer.length - 1,
Math.max(0, nextSectionIndex)
);
});
resizeHandler();
nextSectionIndex = getCurrentSectionIndex();
</script>
#media and screen (min-width: 1050px){
}
body {
overflow-y: hidden;
}
section {
min-width: 100vw!important;
min-height: 100vh!important;
}
}
}
I pasted the first lines of the JS code from documentation about passive Event listeners (official documentation) but i cannot understand how to apply that to my code in order to allow the scrolling in narrow screens.
right now the console output is the following:
Warning: Uncaught ReferenceError: handler is not defined
[Violation] Added non-passive event listener to a scroll-blocking 'touchstart' event. Consider marking event handler as 'passive' to make the page more responsive. See https://www.chromestatus.com/feature/5745543795965952
link to the site: site
thank you in advance for your time.

Related

Simple Gantt Chart in Tabs

I'm trying to create a simple Gantt chart in a tab that is based from this tutorial - https://webdesign.tutsplus.com/tutorials/build-a-simple-gantt-chart-with-css-and-javascript--cms-33813.
I am able to create the timeline in the tabs but the offsetLeft and offsetWidth is not working properly - https://gyazo.com/2e43741d106ecd4eac21d72aa520368a.
I wanted to get the offsetLeft and offsetWidth of each li elements but sometimes it returns a value of zero.
Here is the javascript code:
<script type="text/javascript">
jQuery(function($){
//create chart function
function createChart_tab(e) {
const years_tab = document.querySelectorAll('.research-proj-tabs .chart-years li');
const tasks_tab = document.querySelectorAll('.research-proj-tabs .chart-bars li');
const yearsArray_tab = [...years_tab];
tasks_tab.forEach(el => {
//1
const duration = el.dataset.duration.split("-");
//2
const startYear = duration[0];
const endYear = duration[1];
let left = 0,
width = 0;
//3
if (startYear) {
const filteredArray = yearsArray_tab.filter(year => year.textContent == startYear);
left = filteredArray[0].offsetLeft;
}
// 4
if (endYear) {
const filteredArray = yearsArray_tab.filter(year => year.textContent == endYear);
width = filteredArray[0].offsetLeft + filteredArray[0].offsetWidth - left;
}
// 1
el.style.left = `${left}px`;
el.style.width = `${width}px`;
// 4
if (e.type == "load") {
// 2
el.style.backgroundColor = el.dataset.color;
// 3
el.style.opacity = 1;
}
});
}
//window.addEventListener("onload", createChart);
window.addEventListener("load", createChart_tab);
window.addEventListener("resize", createChart_tab);
});
</script>
While HTML and CSS is following the same layout here: https://webdesign.tutsplus.com/tutorials/build-a-simple-gantt-chart-with-css-and-javascript--cms-33813
I just want to figure out what am I missing here. Thanks!

I use window.scrollBy() for smooth scroll but it doesn't work for Safari [duplicate]

As the title says, it works perfectly fine on Chrome. But in Safari, it just sets the page to the desired top and and left position. Is this the expected behaviour? Is there a way to make it work nicely?
Use smoothscroll polyfill (solution for all browsers), easy applicable and lightweight dependency:
https://github.com/iamdustan/smoothscroll
Once you install it via npm or yarn, add it to your main .js, .ts file (one which executes first)
import smoothscroll from 'smoothscroll-polyfill';
// or if linting/typescript complains
import * as smoothscroll from 'smoothscroll-polyfill';
// kick off the polyfill!
smoothscroll.polyfill();
Behavior options aren't fully supported in IE/Edge/Safari, so you'd have to implement something on your own. I believe jQuery has something already, but if you're not using jQuery, here's a pure JavaScript implementation:
function SmoothVerticalScrolling(e, time, where) {
var eTop = e.getBoundingClientRect().top;
var eAmt = eTop / 100;
var curTime = 0;
while (curTime <= time) {
window.setTimeout(SVS_B, curTime, eAmt, where);
curTime += time / 100;
}
}
function SVS_B(eAmt, where) {
if(where == "center" || where == "")
window.scrollBy(0, eAmt / 2);
if (where == "top")
window.scrollBy(0, eAmt);
}
And if you need horizontal scrolling:
function SmoothHorizontalScrolling(e, time, amount, start) {
var eAmt = amount / 100;
var curTime = 0;
var scrollCounter = 0;
while (curTime <= time) {
window.setTimeout(SHS_B, curTime, e, scrollCounter, eAmt, start);
curTime += time / 100;
scrollCounter++;
}
}
function SHS_B(e, sc, eAmt, start) {
e.scrollLeft = (eAmt * sc) + start;
}
And an example call is:
SmoothVerticalScrolling(myelement, 275, "center");
For a more comprehensive list of methods for smooth scrolling, see my answer here.
window.requestAnimationFrame can be used to perform smooth scrolling in an exact amount of time.
For smooth vertical scrolling, the following function can be used. Note that horizontal scrolling can be done in much the same manner.
/*
#param time: the exact amount of time the scrolling will take (in milliseconds)
#param pos: the y-position to scroll to (in pixels)
*/
function scrollToSmoothly(pos, time) {
var currentPos = window.pageYOffset;
var start = null;
if(time == null) time = 500;
pos = +pos, time = +time;
window.requestAnimationFrame(function step(currentTime) {
start = !start ? currentTime : start;
var progress = currentTime - start;
if (currentPos < pos) {
window.scrollTo(0, ((pos - currentPos) * progress / time) + currentPos);
} else {
window.scrollTo(0, currentPos - ((currentPos - pos) * progress / time));
}
if (progress < time) {
window.requestAnimationFrame(step);
} else {
window.scrollTo(0, pos);
}
});
}
Demo:
/*
#param time: the exact amount of time the scrolling will take (in milliseconds)
#param pos: the y-position to scroll to (in pixels)
*/
function scrollToSmoothly(pos, time) {
var currentPos = window.pageYOffset;
var start = null;
if(time == null) time = 500;
pos = +pos, time = +time;
window.requestAnimationFrame(function step(currentTime) {
start = !start ? currentTime : start;
var progress = currentTime - start;
if (currentPos < pos) {
window.scrollTo(0, ((pos - currentPos) * progress / time) + currentPos);
} else {
window.scrollTo(0, currentPos - ((currentPos - pos) * progress / time));
}
if (progress < time) {
window.requestAnimationFrame(step);
} else {
window.scrollTo(0, pos);
}
});
}
document.querySelector('button').addEventListener('click', function(e){
scrollToSmoothly(500, 1500);
});
html, body {
height: 1000px;
}
<button>Scroll to y-position 500px in 1500ms</button>
For more complex cases, the SmoothScroll.js library can be used, which handles smooth scrolling both vertically and horizontally, scrolling inside other container elements, different easing behaviors, scrolling relatively from the current position, and more. It also supports most browsers that do not have native smooth scrolling.
var easings = document.getElementById("easings");
for(var key in smoothScroll.easing){
if(smoothScroll.easing.hasOwnProperty(key)){
var option = document.createElement('option');
option.text = option.value = key;
easings.add(option);
}
}
document.getElementById('to-bottom').addEventListener('click', function(e){
smoothScroll({yPos: 'end', easing: easings.value, duration: 2000});
});
document.getElementById('to-top').addEventListener('click', function(e){
smoothScroll({yPos: 'start', easing: easings.value, duration: 2000});
});
<script src="https://cdn.jsdelivr.net/gh/LieutenantPeacock/SmoothScroll#1.2.0/src/smoothscroll.min.js" integrity="sha384-UdJHYJK9eDBy7vML0TvJGlCpvrJhCuOPGTc7tHbA+jHEgCgjWpPbmMvmd/2bzdXU" crossorigin="anonymous"></script>
<!-- Taken from one of the library examples -->
Easing: <select id="easings"></select>
<button id="to-bottom">Scroll To Bottom</button>
<br>
<button id="to-top" style="margin-top: 5000px;">Scroll To Top</button>
The workarounds above all make up for the lack of Safari support for behaviors.
It's still necessary to detect when a workaround is needed.
This little function will detect if smooth scrolling is supported by the browser. It returns false on Safari, true on Chrome and Firefox:
// returns true if browser supports smooth scrolling
const supportsSmoothScrolling = () => {
const body = document.body;
const scrollSave = body.style.scrollBehavior;
body.style.scrollBehavior = 'smooth';
const hasSmooth = getComputedStyle(body).scrollBehavior === 'smooth';
body.style.scrollBehavior = scrollSave;
return hasSmooth;
};
const pre = document.querySelector('pre');
// returns true if browser supports smooth scrolling
const supportsSmoothScrolling = () => {
const body = document.body;
const scrollSave = body.style.scrollBehavior;
body.style.scrollBehavior = 'smooth';
const hasSmooth = getComputedStyle(body).scrollBehavior === 'smooth';
body.style.scrollBehavior = scrollSave;
return hasSmooth;
};
const supported = supportsSmoothScrolling();
pre.innerHTML = `supported: ${ (supported) ? 'true' : 'false'}`;
<h3>
Testing if 'scrollBehavior smooth' is supported
</h3>
<pre></pre>
Update
Test of Safari Technology Preview, Release 139 (Safari 15.4) shows support for scrollBehavior smooth, so we may expect to see support in 15.4.
The solution with the smoothest performance, especially if you want to incorporate easing is to use requestAnimationFrame:
const requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
const step = (timestamp) => {
window.scrollBy(
0,
1, // or whatever INTEGER you want (this controls the speed)
);
requestAnimationFrame(step);
};
requestAnimationFrame(step);
if you want to later cancel the scroll, you need to have a reference to your requestAnimationFrame (do this everywhere you use requestAnimationFrame(step)):
this.myRequestAnimationFrame = requestAnimationFrame(step);
const cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame;
cancelAnimationFrame(this.myRequestAnimationFrame);
Now what if you want to use easing with your scroll and take timeouts between scroll actions?
create an array of 60 elements (requestAnimationFrame usually calls 60 times per second. It's technically whatever the refresh rate of the browser is, but 60 is the most common number.) We are going to fill this array non-linearly then use those numbers to control how much to scroll at each step of requestAnimationFrame:
let easingPoints = new Array(60).fill(0)
choose an easing function. Let's say we're doing a cubic ease-out:
function easeCubicOut(t) {
return --t * t * t + 1;
}
create a dummy array and fill it with data piped through the easing function. You'll see why we need this in a moment:
// easing function will take care of decrementing t at each call (too lazy to test it at the moment. If it doesn't, just pass it a decrementing value at each call)
let t = 60;
const dummyPoints = new Array(60).fill(0).map(()=> easeCubicOut(t));
const dummyPointsSum = dummyPoints.reduce((a, el) => {
a += el;
return a;
}, 0);
map easingPoints using the help of each dummyPoint ratio to dummyPointsSum:
easingPoints = easingPoints.map((el, i) => {
return Math.round(MY_SCROLL_DISTANCE * dummyPoints[i] / dummyPointsSum);
});
in your scroll function, we'll make a few adjustments:
const requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
let i = 0;
const step = (timestamp) => {
window.scrollBy(
0,
easingPoints[i],
);
if (++i === 60) {
i = 0;
return setTimeout(() => {
this.myRequestAnimationFrame = requestAnimationFrame(step);
}, YOUR_TIMEOUT_HERE);
}
};
this.myRequestAnimationFrame = requestAnimationFrame(step);
A simple jQuery fix that works for Safari:
$('a[href*="#"]').not('[href="#"]').not('[href="#0"]').click(function (t) {
if (location.pathname.replace(/^\//, "") == this.pathname.replace(/^\//, "") && location.hostname == this.hostname) {
var e = $(this.hash);
e = e.length ? e : $("[name=" + this.hash.slice(1) + "]"), e.length && (t.preventDefault(), $("html, body").animate({
scrollTop: e.offset().top
}, 600, function () {
var t = $(e);
if (t.focus(), t.is(":focus")) return !1;
t.attr("tabindex", "-1"), t.focus()
}))
}
});
Combining the answers of George Daniel and terrymorse, the following can be used for all the browser's support using native JavaScript.
As, Chrome, Firefox supports CSS, scroll-behavior: smooth; for the browsers which don't support this property, we can add below.
HTML:
<a onclick="scrollToSection(event)" href="#section">
Redirect On section
</a>
<section id="section">
Section Content
</section>
CSS:
body {
scroll-behavior: smooth;
}
JavaScript:
function scrollToSection(event) {
if (supportsSmoothScrolling()) {
return;
}
event.preventDefault();
const scrollToElem = document.getElementById("section");
SmoothVerticalScrolling(scrollToElem, 300, "top");
}
function supportsSmoothScrolling() {
const body = document.body;
const scrollSave = body.style.scrollBehavior;
body.style.scrollBehavior = 'smooth';
const hasSmooth = getComputedStyle(body).scrollBehavior === 'smooth';
body.style.scrollBehavior = scrollSave;
return hasSmooth;
};
function SmoothVerticalScrolling(element, time, position) {
var eTop = element.getBoundingClientRect().top;
var eAmt = eTop / 100;
var curTime = 0;
while (curTime <= time) {
window.setTimeout(SVS_B, curTime, eAmt, position);
curTime += time / 100;
}
}
function SVS_B(eAmt, position) {
if (position == "center" || position == "")
window.scrollBy(0, eAmt / 2);
if (position == "top")
window.scrollBy(0, eAmt);
}
Another possible solution with an "ease-out" effect.
Inspired by some of the answers given earlier,
a key difference is in using "pace" instead of specifying a duration, I found that calculating the length of each step based on a fixed pace creates a smooth "ease-out" effect as the number of steps increases as the scroll approaches the destination point.
Hopefully the code below is easy to understand.
function smoothScrollTo(destination) {
//check if browser supports smooth scroll
if (window.CSS.supports('scroll-behavior', 'smooth')) {
window.scrollTo({ top: destination, behavior: 'smooth' });
} else {
const pace = 200;
let prevTimestamp = performance.now();
let currentPos = window.scrollY;
// #param: timestamp is a "DOMHightResTimeStamp", check on MDN
function step(timestamp) {
let remainingDistance = currentPos < destination ? destination - currentPos : currentPos - destination;
let stepDuration = timestamp - prevTimestamp;
let numOfSteps = pace / stepDuration;
let stepLength = remainingDistance / numOfSteps;
currentPos = currentPos < destination ? currentPos + stepLength : currentPos - stepLength;
window.scrollTo({ top: currentPos });
prevTimestamp = timestamp;
if (Math.floor(remainingDistance) >= 1) window.requestAnimationFrame(step);
}
window.requestAnimationFrame(step);
}
}
This is my first contribution on SO after years of benefiting from this great community. Constructive criticism is highly appreciated.
Thanks to T.Dayya, I had combined few answers on that topic and here is ts module with
extension function scrollSmoothIntoView.
export default {}
declare global {
interface Element {
scrollSmoothIntoView(): void;
}
}
Element.prototype.scrollSmoothIntoView = function()
{
const t = 45;
const tstep = 6.425/t;
const dummyPoints = new Array(t).fill(0).map((t, i) => circ(i * tstep));
const dummyPointsSum = dummyPoints.reduce((a, el) => { a += el; return a;}, 0);
const _window: any = window;
const _elem: any = getScrollParent(this);
const scroll_distance: any = (this as any).offsetTop - (!_elem.parentElement ? _window.scrollY : 0);
let easingPoints = new Array(t).fill(0)
easingPoints = easingPoints.map((el, i) => {
return Math.round(scroll_distance * dummyPoints[i] / dummyPointsSum);
});
const requestAnimationFrame = _window.requestAnimationFrame ||
_window.mozRequestAnimationFrame ||
_window.webkitRequestAnimationFrame ||
_window.msRequestAnimationFrame;
let i = 0;
const step = (timestamp:any) => {
_elem.scrollBy(0, easingPoints[i]);
if (++i < t)
setTimeout(() => { requestAnimationFrame(step) }, 2);
};
window.requestAnimationFrame(()=>requestAnimationFrame(step));
}
function getScrollParent(element: any, includeHidden?: any):any {
var style = getComputedStyle(element);
var excludeStaticParent = style.position === "absolute";
var overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/;
if (style.position === "fixed") return document.body;
for (var parent = element; (parent = parent.parentElement);) {
style = getComputedStyle(parent);
if (excludeStaticParent && style.position === "static") {
continue;
}
if (overflowRegex.test(style.overflow + style.overflowY + style.overflowX)) return parent;
}
return document.body;
}
function circ(t:any) {
return 1+Math.cos(3+t);
}
Using html_element.scrollSmoothIntoView().

Extending Touch EventListener to Additional DOM Element

I used a Codrops article/experiment to create an interactive environment for a local group to use at their conferences. The problem with this is the default interaction is not very intuitive. The template used Flickity.js and what seems like classie.js to create this sliding interface I am having trouble with.
The page can be found here:
www.eyeconic.tv/ky-ffa/
Issue: The only way to activate the view-full is by clicking on the html element:
<h2 class=".stack-title">
// After the stack is active you should be able to activate the full view by clicking on the first .stack-item used to create the thumbnail below it. This entire div should be clickable. Users are touching everywhere all over the screen and not actually clicking the title for the desired action. I hope this makes sense.
In other words you should be able to click the stack-title and the image below the title of each stack to pull the stack into the full view mode on the screen. Then click the x or anywhere else on the screen to close the full view.
The following is located in main.js and the reference I found to create the events I am referring to.
//
function initEvents() {
stacks.forEach(function(stack) {
var titleEl = stack.querySelector('.stack-title');
// expand/close the stack
titleEl.addEventListener('click', function(ev) {
ev.preventDefault();
if( classie.has(stack, 'is-selected') ) { // current stack
if( classie.has(bodyEl, 'view-full') ) { // stack is opened
var closeStack = function() {
classie.remove(bodyEl, 'move-items');
onEndTransition(slider, function() {
classie.remove(bodyEl, 'view-full');
bodyEl.style.height = '';
flkty.bindDrag();
flkty.options.accessibility = true;
canMoveHeroImage = true;
});
};
// if the user scrolled down, let's first scroll all up before closing the stack.
var scrolled = scrollY();
if( scrolled > 0 ) {
smooth_scroll_to(isFirefox ? docElem : bodyEl || docElem, 0, 500).then(function() {
closeStack();
});
}
else {
closeStack();
}
}
else if( canOpen ) { // stack is closed
canMoveHeroImage = false;
classie.add(bodyEl, 'view-full');
setTimeout(function() { classie.add(bodyEl, 'move-items'); }, 25);
bodyEl.style.height = stack.offsetHeight + 'px';
flkty.unbindDrag();
flkty.options.accessibility = false;
}
}
else if( classie.has(stack, 'stack-prev') ) {
flkty.previous(true);
}
else if( classie.has(stack, 'stack-next') ) {
flkty.next(true);
}
});
titleEl.addEventListener('mouseenter', function(ev) {
if( classie.has(stack, 'is-selected') ) {
canMoveHeroImage = false;
imghero.style.WebkitTransform = 'perspective(1000px) translate3d(0,0,0) rotate3d(1,1,1,0deg)';
imghero.style.transform = 'perspective(1000px) translate3d(0,0,0) rotate3d(1,1,1,0deg)';
}
});
titleEl.addEventListener('mouseleave', function(ev) {
// if current stack and it's not opened..
if( classie.has(stack, 'is-selected') && !classie.has(bodyEl, 'view-full') ) {
canMoveHeroImage = true;
}
});
});
window.addEventListener('mousemove', throttle(function(ev) {
if( !canMoveHeroImage ) return false;
var xVal = -1/(win.height/2)*ev.clientY + 1,
yVal = 1/(win.width/2)*ev.clientX - 1,
transX = 20/(win.width)*ev.clientX - 10,
transY = 20/(win.height)*ev.clientY - 10,
transZ = 100/(win.height)*ev.clientY - 50;
imghero.style.WebkitTransform = 'perspective(1000px) translate3d(' + transX + 'px,' + transY + 'px,' + transZ + 'px) rotate3d(' + xVal + ',' + yVal + ',0,2deg)';
imghero.style.transform = 'perspective(1000px) translate3d(' + transX + 'px,' + transY + 'px,' + transZ + 'px) rotate3d(' + xVal + ',' + yVal + ',0,2deg)';
}, 100));
// window resize
window.addEventListener( 'resize', throttle(function(ev) {
// recalculate window width/height
win = { width: window.innerWidth, height: window.innerHeight };
// reset body height if stack is opened
if( classie.has(bodyEl, 'view-full') ) { // stack is opened
bodyEl.style.height = stacks[flkty.selectedIndex].offsetHeight + 'px';
}
}, 50));
// Flickity events:
flkty.on('cellSelect', function() {
canOpen = false;
classie.remove(bodyEl, 'item-clickable');
var prevStack = stacksWrapper.querySelector('.stack-prev'),
nextStack = stacksWrapper.querySelector('.stack-next'),
selidx = flkty.selectedIndex,
cellsCount = flkty.cells.length,
previdx = selidx > 0 ? selidx - 1 : cellsCount - 1;
nextidx = selidx < cellsCount - 1 ? selidx + 1 : 0;
if( prevStack ) {
classie.remove(prevStack, 'stack-prev');
}
if( nextStack ) {
classie.remove(nextStack, 'stack-next');
}
classie.add(stacks[previdx], 'stack-prev');
classie.add(stacks[nextidx], 'stack-next');
});
flkty.on('dragStart', function() {
canOpen = false;
classie.remove(bodyEl, 'item-clickable');
});
flkty.on('settle', function() {
classie.add(bodyEl, 'item-clickable');
canOpen = true;
});
}
init();
})();
I wrapped the title and the first stack item in a div class .touch-me and it worked fairly well. I had previously tried to do this and received an error. But I may have mistyped something because it only made sense.
ISSUE: It works on mouseclick, but it is not working with touch on windows. I have untested it in any other environment because it will be deployed on a windows touch screen.
Although I cannot tell the layer not to close on touch when you swipe or touch the header image for the stack.... I'm afraid I do not have the skillset to properly modify the logic in the javascript since I do not entirely understand the plugins being used.

How do I listen for triple clicks in JavaScript?

If this is for a double-click:
window.addEventListener("dblclick", function(event) { }, false);
How can I capture a triple-click? This is for a pinned tab in Google Chrome.
You need to write your own triple-click implementation because no native event exists to capture 3 clicks in a row. Fortunately, modern browsers have event.detail, which the MDN documentation describes as:
A count of consecutive clicks that happened in a short amount of time, incremented by one.
This means you can simply check the value of this property and see if it is 3:
window.addEventListener('click', function (evt) {
if (evt.detail === 3) {
alert('triple click!');
}
});
Working demo: http://jsfiddle.net/L6d0p4jo/
If you need support for IE 8, the best approach is to capture a double-click, followed by a triple-click — something like this, for example:
var timer, // timer required to reset
timeout = 200; // timer reset in ms
window.addEventListener("dblclick", function (evt) {
timer = setTimeout(function () {
timer = null;
}, timeout);
});
window.addEventListener("click", function (evt) {
if (timer) {
clearTimeout(timer);
timer = null;
executeTripleClickFunction();
}
});
Working demo: http://jsfiddle.net/YDFLV/
The reason for this is that old IE browsers will not fire two consecutive click events for a double click. Don't forget to use attachEvent in place of addEventListener for IE 8.
Since DOM Level 2 you could use mouse click handler and check the detail parameter of event which should be interpreted as:
The detail attribute inherited from UIEvent indicates the number of times a mouse button has been pressed and released over the same screen location during a user action. The attribute value is 1 when the user begins this action and increments by 1 for each full sequence of pressing and releasing. If the user moves the mouse between the mousedown and mouseup the value will be set to 0, indicating that no click is occurring.
So the value of detail === 3 will give you the triple-click event.
More information in specification at http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-MouseEvent.
Thanks to #Nayuki https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/detail - a DOM3 extension which is WIP https://w3c.github.io/uievents/
Here is the real Triple click event, which triggers only when all of three clicks fired with equal interval.
// Default settings
var minClickInterval = 100,
maxClickInterval = 500,
minPercentThird = 85.0,
maxPercentThird = 130.0;
// Runtime
var hasOne = false,
hasTwo = false,
time = [0, 0, 0],
diff = [0, 0];
$('#btn').on('click', function() {
var now = Date.now();
// Clear runtime after timeout fot the 2nd click
if (time[1] && now - time[1] >= maxClickInterval) {
clearRuntime();
}
// Clear runtime after timeout fot the 3rd click
if (time[0] && time[1] && now - time[0] >= maxClickInterval) {
clearRuntime();
}
// Catch the third click
if (hasTwo) {
time[2] = Date.now();
diff[1] = time[2] - time[1];
var deltaPercent = 100.0 * (diff[1] / diff[0]);
if (deltaPercent >= minPercentThird && deltaPercent <= maxPercentThird) {
alert("Triple Click!");
}
clearRuntime();
}
// Catch the first click
else if (!hasOne) {
hasOne = true;
time[0] = Date.now();
}
// Catch the second click
else if (hasOne) {
time[1] = Date.now();
diff[0] = time[1] - time[0];
(diff[0] >= minClickInterval && diff[0] <= maxClickInterval) ?
hasTwo = true : clearRuntime();
}
});
var clearRuntime = function() {
hasOne = false;
hasTwo = false;
time[0] = 0;
time[1] = 0;
time[2] = 0;
diff[0] = 0;
diff[1] = 0;
};
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Click button three times with equal interval
<button id="btn">Click me</button>
Also, I wrote jquery plugin TrplClick, which enables 'trplclick' event
it's very simple if you do it right, and you can even catch single, double, triple, ... clicks as you like. plain javascript, customizable click delay (timeout):
var clicks = 0;
var timer, timeout = 350;
var doubleClick = function(e) {
console.log('doubleClick');
}
var tripleClick = function(e) {
console.log('tripleClick');
}
// click timer
yourcontainer.addEventListener('click', function(e) {
clearTimeout(timer);
clicks++;
var evt = e;
timer = setTimeout(function() {
if(clicks==2) doubleClick(evt);
if(clicks==3) tripleClick(evt);
clicks = 0;
}, timeout);
});
pseudo-code:
var clicks = 0
onclick:
clicks++;
setTimer(resetClicksToZero);
if clicks == 3: tripleclickdetected(); clicks = 0;
I am working on a javascript code editor and I had to listen for triple click and here is the solution that will work for most browsers:
// Function to get mouse position
var getMousePosition = function (mouseEvent) {
var currentObject = container;
var currentLeft = 0;
var currentTop = 0;
do {
currentLeft += currentObject.offsetLeft;
currentTop += currentObject.offsetTop;
currentObject = currentObject.offsetParent;
} while (currentObject != document.body);
return {
x: mouseEvent.pageX - currentLeft,
y: mouseEvent.pageY - currentTop
}
}
// We will need a counter, the old position and a timer
var clickCounter = 0;
var clickPosition = {
x: null,
y: null
};
var clickTimer;
// The listener (container may be any HTML element)
container.addEventListener('click', function (event) {
// Get the current mouse position
var mousePosition = getMousePosition(event);
// Function to reset the data
var resetClick = function () {
clickCounter = 0;
var clickPosition = {
x: null,
y: null
};
}
// Function to wait for the next click
var conserveClick = function () {
clickPosition = mousePosition;
clearTimeout(clickTimer);
clickTimer = setTimeout(resetClick, 250);
}
// If position has not changed
if (clickCounter && clickPosition.x == mousePosition.x && clickPosition.y == mousePosition.y) {
clickCounter++;
if (clickCounter == 2) {
// Do something on double click
} else {
// Do something on triple click
resetClick();
}
conserveClick();
} else {
// Do something on single click
conserveClick();
}
});
Tested on Firefox 12, Google Chrome 19, Opera 11.64, Internet Explorer 9
This approach checks if the user has not changed cursor's position, you still can do something when you have single click or double click. Hope this solution will help everybody who will need to implement a triple click event listener :)
Configurable n-clicks event detector factory
const nClicks = (minClickStreak, maxClickInterval = 500, resetImmediately = true) => {
let timerId = 0
let clickCount = 0
let lastTarget = null
const reset = () => {
timerId = 0
clickCount = 0
lastTarget = null
}
return (originalEventHandler) => (e) => {
if (lastTarget == null || lastTarget == e.target) { // 2. unless we clicked same target
clickCount++ // 3. then increment click count
clearTimeout(timerId)
}
lastTarget = e.target
timerId = setTimeout(reset, maxClickInterval) // 1. reset state within set time
if (clickCount >= minClickStreak) {
originalEventHandler(e)
if (resetImmediately) {
clickCount = 0
}
}
}
}
Usage
table.addEventListener('click', nClicks(2)(e => { // double click
selectCell(e.target)
}))
table.addEventListener('click', nClicks(3)(e => { // triple click
selectRow(e.target)
}))

JQuery menu float and display submenus on page

This is my first time using JQuery in any of my projects.
I have implemented the superfish menu.
On some of my pages I have a horizontal scroll. I would like to make the menu float on the center of the page as the page is scrolled.
Also I need to make sure that the submenu on the far right hand side of the menu does not open up off the page. When I hover on the right most element it opens up half off the page.
Any ideas on how to fix these two things?
I'm perfectly willing to use a different Jquery menu if there is a better one that has these features built in...
Thanks!
javascrupt call in my page:
$(document).ready(function () {
$("ul.sf-menu").supersubs({
minWidth: 12, // minimum width of sub-menus in em units
maxWidth: 27, // maximum width of sub-menus in em units
extraWidth: 1 // extra width can ensure lines don't sometimes turn over
// due to slight rounding differences and font-family
}).superfish({ animation: { opacity: 'show', height: 'show' }, autoArrows: false }); // call supersubs first, then superfish, so that subs are
// not display:none when measuring. Call before initialising
// containing tabs for same reason.
I can post any more code that is needed, but there is quite a lot of code in the superfish files so i'm not sure what I should post.
I found this script and it works well, however when I scroll right the horizonal menu starts to stack so the menu items are side by side rather then vertical. I want to modify this to keep the menu horizonal...
<script type="text/javascript"><!--
var floatingMenuId = 'floatdiv';
var floatingMenu =
{
targetX: -1000,
targetY: 10,
hasInner: typeof (window.innerWidth) == 'number',
hasElement: document.documentElement
&& document.documentElement.clientWidth,
menu:
document.getElementById
? document.getElementById(floatingMenuId)
: document.all
? document.all[floatingMenuId]
: document.layers[floatingMenuId]
};
floatingMenu.move = function () {
if (document.layers) {
floatingMenu.menu.left = floatingMenu.nextX;
floatingMenu.menu.top = floatingMenu.nextY;
}
else {
floatingMenu.menu.style.left = floatingMenu.nextX + 'px';
floatingMenu.menu.style.top = floatingMenu.nextY + 'px';
}
}
floatingMenu.computeShifts = function () {
var de = document.documentElement;
floatingMenu.shiftX =
floatingMenu.hasInner
? pageXOffset
: floatingMenu.hasElement
? de.scrollLeft
: document.body.scrollLeft;
if (floatingMenu.targetX < 0) {
if (floatingMenu.hasElement && floatingMenu.hasInner) {
// Handle Opera 8 problems
floatingMenu.shiftX +=
de.clientWidth > window.innerWidth
? window.innerWidth
: de.clientWidth
}
else {
floatingMenu.shiftX +=
floatingMenu.hasElement
? de.clientWidth
: floatingMenu.hasInner
? window.innerWidth
: document.body.clientWidth;
}
}
floatingMenu.shiftY =
floatingMenu.hasInner
? pageYOffset
: floatingMenu.hasElement
? de.scrollTop
: document.body.scrollTop;
if (floatingMenu.targetY < 0) {
if (floatingMenu.hasElement && floatingMenu.hasInner) {
// Handle Opera 8 problems
floatingMenu.shiftY +=
de.clientHeight > window.innerHeight
? window.innerHeight
: de.clientHeight
}
else {
floatingMenu.shiftY +=
floatingMenu.hasElement
? document.documentElement.clientHeight
: floatingMenu.hasInner
? window.innerHeight
: document.body.clientHeight;
}
}
}
floatingMenu.doFloat = function () {
var stepX, stepY;
floatingMenu.computeShifts();
stepX = (floatingMenu.shiftX +
floatingMenu.targetX - floatingMenu.nextX) * .07;
if (Math.abs(stepX) < .5) {
stepX = floatingMenu.shiftX +
floatingMenu.targetX - floatingMenu.nextX;
}
stepY = (floatingMenu.shiftY +
floatingMenu.targetY - floatingMenu.nextY) * .07;
if (Math.abs(stepY) < .5) {
stepY = floatingMenu.shiftY +
floatingMenu.targetY - floatingMenu.nextY;
}
if (Math.abs(stepX) > 0 ||
Math.abs(stepY) > 0) {
floatingMenu.nextX += stepX;
floatingMenu.nextY += stepY;
floatingMenu.move();
}
setTimeout('floatingMenu.doFloat()', 20);
};
// addEvent designed by Aaron Moore
floatingMenu.addEvent = function (element, listener, handler) {
if (typeof element[listener] != 'function' ||
typeof element[listener + '_num'] == 'undefined') {
element[listener + '_num'] = 0;
if (typeof element[listener] == 'function') {
element[listener + 0] = element[listener];
element[listener + '_num']++;
}
element[listener] = function (e) {
var r = true;
e = (e) ? e : window.event;
for (var i = element[listener + '_num'] - 1; i >= 0; i--) {
if (element[listener + i](e) == false)
r = false;
}
return r;
}
}
//if handler is not already stored, assign it
for (var i = 0; i < element[listener + '_num']; i++)
if (element[listener + i] == handler)
return;
element[listener + element[listener + '_num']] = handler;
element[listener + '_num']++;
};
floatingMenu.init = function () {
floatingMenu.initSecondary();
floatingMenu.doFloat();
};
// Some browsers init scrollbars only after
// full document load.
floatingMenu.initSecondary = function () {
floatingMenu.computeShifts();
floatingMenu.nextX = floatingMenu.shiftX +
floatingMenu.targetX;
floatingMenu.nextY = floatingMenu.shiftY +
floatingMenu.targetY;
floatingMenu.move();
}
if (document.layers)
floatingMenu.addEvent(window, 'onload', floatingMenu.init);
else {
floatingMenu.init();
floatingMenu.addEvent(window, 'onload',
floatingMenu.initSecondary);
}
</script>
I'm not sure on how you mean centering, but if you mean horizontally centered:
Could you separate the main page (that horizontally overflows) and the menu into separate div's? e.g.
<div id="menu"><center><ul class="sf-menu">...</ul></center></div>
<div id="mainpage" style="overflow:auto;">Contents goes here</div>
(the <center> tag might have to be <div style="width:X;margin:0 auto;"> depending on how superfish works)
On the menu going over the page, sorry I'll have to defer to someone more knowable to answer that.

Categories