Cytoscape JS Layout Loading Spinner - javascript

I want to display a loading spinner while a huge graph is loaded and layouted in Cytoscape JS. But the loading spinner disappears even though the layout is not finished. I am wondering if there is a way to listen to a layout finish and show the spinner until the final layout is reached ?
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/2.7.14/cytoscape.js"></script>
<title></title>
<style media="screen">
#cy {
position: absolute;
width:100%;
height:100%;
/*height:500px;*/
z-index: 0;
margin-left: auto;
margin-right: auto;
}
#loading {
position: absolute;
background: #ffffff;
display: block;
left: 0;
top: 50%;
width: 100%;
text-align: center;
margin-top: -0.5em;
font-size: 2em;
color: #000;
}
#loading.loaded {
display: none;
}
</style>
</head>
<body>
<div id="cy">
</div>
<div id="loading">
<span class="fa fa-refresh fa-spin"></span>
</div>
<script type="text/javascript">
$(document).ready(function(){
// Create random JSON object
var maximum = 500;
var minimum = 1;
function getRandNumber(){
var min = 1;
var max = 1000;
var randNumber = Math.floor(Math.random() * (max - min + 1)) + min;
return randNumber;
}
nodes = [];
geneIds = [];
edges = [];
for(var i = 0; i < 2000; i++){
var source = getRandNumber();
var target = getRandNumber();
edges.push({"data": {"id":i.toString(),"source":source.toString(),"target":target.toString()}});
if ($.inArray(source, geneIds) === -1) {
nodes.push({"data": {"id":source.toString(),"name":source.toString()}});
geneIds.push(source);
}
if ($.inArray(target, geneIds) === -1) {
nodes.push({"data":{"id":target.toString(),"name":target.toString()}});
geneIds.push(target);
}
}
var networkData = {"nodes":nodes,"edges":edges};
// console.log(networkData);
///////////////// Create the network
var coseLayoutParams = {
name: 'cose',
// padding: 10,
randomize: false,
};
var cy = window.cy = cytoscape({
container: document.getElementById('cy'),
// elements: networkData,
minZoom: 0.1,
// maxZoom: 10,
wheelSensitivity: 0.2,
style: [
{
selector: 'node',
style: {
'content': 'data(name)',
'text-valign': 'center',
'text-halign': 'center',
'font-size': 8
}
}],
layout: coseLayoutParams
});
cy.add(networkData);
var layout = cy.makeLayout(coseLayoutParams);
layout.run();
$("#loading").addClass("loaded");
});
</script>
</body>
</html>

You can add a listener to your layout object that waits for 'layoutstop' event to be fired:
layout.on('layoutstop', function() {
//... unload spinner here
});
see here: http://js.cytoscape.org/#layout.on
and here: https://github.com/cytoscape/cytoscape.js/blob/master/documentation/md/events.md
or, you can specify a callback function in layout options, such as
var coseLayoutParams = {
name: 'cose',
// padding: 10,
randomize: false,
// Called on `layoutstop`
stop: function() {
//... unload spinner here
},
};
see here: http://js.cytoscape.org/#layouts/cose

Related

html input with cytoscape.js fails to open the dialog window

I am building an interactive graph app where the user would upload a data file. The app would then build the graph from the input file.
I am having problems with making the input work with the cytoscape . More specifically, the file dialog window does not pop up when I include the div. That is, the input button is not responsive unless I comment out the cytoscape .
Please see the code below. I guess, this is not cytoscape specific. However, I could not get to the bottom of this, so I am posting the complete instance of the problem.
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Test</title>
<script src="cytoscape.js"></script>
</head>
<style>
#cy {
width: 100%;
height: 100%;
position: absolute;
top: 0px;
left: 0px;
}
</style>
<body>
<form name="uploadForm">
<div>
<input id="uploadInput" type="file" name="myFiles" multiple>
selected files: <span id="fileNum">0</span>;
total size: <span id="fileSize">0</span>
</div>
<div><input type="submit" value="Send file"></div>
</form>
<div>
<h1>This!</h1>
</div>
<div id="cy"></div>
<script>
function updateSize() {
let nBytes = 0,
oFiles = this.files,
nFiles = oFiles.length;
for (let nFileId = 0; nFileId < nFiles; nFileId++) {
nBytes += oFiles[nFileId].size;
}
let sOutput = nBytes + " bytes";
// optional code for multiples approximation
const aMultiples = ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"];
for (nMultiple = 0, nApprox = nBytes / 1024; nApprox > 1; nApprox /= 1024, nMultiple++) {
sOutput = nApprox.toFixed(3) + " " + aMultiples[nMultiple] + " (" + nBytes + " bytes)";
}
// end of optional code
document.getElementById("fileNum").innerHTML = nFiles;
document.getElementById("fileSize").innerHTML = sOutput;
console.log("AAAA!")
}
document.getElementById("uploadInput").addEventListener("change", updateSize, false);
</script>
<script>
// https://blog.js.cytoscape.org/2016/05/24/getting-started/
var cy = cytoscape({
container: document.getElementById('cy'),
// ~~~~~~~~~~~~~~~~~~~
elements: [
{ data: { id: 'a' } },
{ data: { id: 'b' } },
{
data: {
id: 'ab',
source: 'a',
target: 'b'
}
}],
// ~~~~~~~~~~~~~~~~~~~
style: [
{
selector: 'node',
style: {
shape: 'hexagon',
'background-color': 'red',
label: 'data(id)'
}
}]
});
cy.layout({
name: 'circle'
}).run();
</script>
</body>
</html>
It's related with the z-index of the divs. In your case, because you don't assign z-indexes to the divs and add cy div at the end, it is rendered on top, so you cannot interact with the buttons.
You can assign a z-index to cy div such as -1 to send it to the background:
#cy {
width: 100%;
height: 100%;
position: absolute;
top: 0px;
left: 0px;
z-index: -1;
}
But in this case you won't be able to interact with the top part of the cy canvas. A better solution is to change the top attribute to some value such as 250px so that cy canvas is started to be rendered below the buttons and texts:
#cy {
width: 100%;
height: 100%;
position: absolute;
top: 250px;
left: 0px;
}

how to add text between handles in a range slider

I am using noUiSlider, but in fact any range slider library would work.
I have a small widget (here: https://codepen.io/chapkovski/pen/pobRwZj) where people have to split the range into three sections:
var slider = document.getElementById('slider-color');
noUiSlider.create(slider, {
start: [6000, 12000],
connect: [true, true, true],
range: {
'min': [2000],
'max': [20000]
}
});
var connect = slider.querySelectorAll('.noUi-connect');
var classes = ['c-1-color', 'c-2-color', 'c-3-color'];
for (var i = 0; i < connect.length; i++) {
connect[i].classList.add(classes[i]);
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/noUiSlider/14.6.2/nouislider.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/noUiSlider/14.6.2/nouislider.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<style>
.c-1-color {
background: red;
}
.c-2-color {
background: yellow;
}
.c-3-color {
background: green;
}
</style>
<div id='slider-color'></div>
What I can't figure is how to add a text within the ranges. For instance for the mid-range (yellow one) would be something like 'Second category: XX%' (depending on the handles position. When I do something like:
.c-3-color::after {
content:'my text goes here';
}
it ends up completely deformed.
On the parent you are having scale so this is why it is growing. You need to compensate that with child or pseudo child Please have a look as below:
.c-3-color::after {
content:'my text goes here';
display: block;
position: absolute;
top: 50%;
left: 33%;
transform: translateY(-50%) scale(4,2);
}
DEMO (with adjustment of the code above because line is smaller
var slider = document.getElementById('slider-color');
noUiSlider.create(slider, {
start: [6000, 12000],
connect: [true, true, true],
range: {
'min': [2000],
'max': [20000]
}
});
var connect = slider.querySelectorAll('.noUi-connect');
var classes = ['c-1-color', 'c-2-color', 'c-3-color'];
for (var i = 0; i < connect.length; i++) {
connect[i].classList.add(classes[i]);
}
.c-3-color::after {
content:'my text goes here';
display: block;
position: absolute;
top: 50%;
left: 33%;
transform: translateY(-50%) scale(3,2);
font-size:12px;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/noUiSlider/14.6.2/nouislider.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/noUiSlider/14.6.2/nouislider.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<style>
.c-1-color {
background: red;
}
.c-2-color {
background: yellow;
}
.c-3-color {
background: green;
}
</style>
<div id='slider-color'></div>

Javascript: is it possible to determine how much user scrolls after reaching the end of a page?

On mobile, it's a common UI pattern to have a scrollable element inside a draggable element. When you reach the end of the scrollable element, you start dragging the outer element. E.g. in this GIF (https://media.giphy.com/media/9MJgBkoZfqA7jRdQop/giphy.gif), after scrolling to the top, if you continuing scrolling, it'll drag the subreddits menu.
I want to implement a similar pattern using JS/CSS. To do this, I need to detect if users continue scrolling after reaching the end. Is this possible? If so, is it possible to determine how much they scroll after reaching the end?
window.onscroll = function(element) {
if ((window.innerHeight + window.pageYOffset) >= document.body.offsetHeight) {
alert("you're at the bottom of the page");
}
};
Using element parameter to know the current exact x y where mouse is now at to calculate more and some how much was scrolled
Javascript: How to detect if browser window is scrolled to bottom?
If You need to keep track of the user activity after the bottom (or the top) of the page has been reached, beside the scroll event, You need to track the the wheel event. Moreover, on mobile, You need to track also touchstart and touchmove events.
Not all these events are normalized across browsers, so I did my own normalization function, which is more or less something like this:
var compulsivity = Math.log2(Math.max(scrollAmount, 0.01) * wheelAmount);
Below is a complete playground. You can test it in Chrome using the Mobile View of the Developer Tools, or in other browsers using the TouchEmulator.
function Tracker(page) {
this.page = page;
this.moveUp = 0;
this.moveDown = 0;
this.startTouches = {};
this.moveTouches = {};
this.lastScrollY = 0;
this.monitor = {};
this.startThreshold = 160;
this.moveThreshold = 10;
this.iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
this.pullToRefresh = window.chrome || navigator.userAgent.match('CriOS');
this.amplitude = 16 / Math.log(2);
this.page.ownerDocument.addEventListener( 'onwheel' in document ? 'wheel' : 'onmousewheel' in document ? 'mousewheel' : 'DOMMouseScroll', this, { passive: true } );
/* The basic scroll event cannot be canceled, so it does not need to be set passive.*/
this.page.ownerDocument.addEventListener('scroll', this);
this.page.addEventListener('touchstart', this, { passive: true });
/* Maybe we need to cancel pullToRefresh */
this.page.addEventListener('touchmove', this, { passive: false });
return this;
}
Tracker.prototype.handleEvent = function (e) { /* handleEvent is built-in */
var winHeight = (this.iOS ? document.documentElement.clientHeight : window.innerHeight) | 0,
currScrollY = window.pageYOffset | 0,
amountScrollY = (this.lastScrollY - currScrollY) | 0,
elHeight = this.page.offsetHeight | 0,
elTop = -currScrollY, elBottom = winHeight - elHeight + currScrollY,
isTop = elTop >= 0, isBottom = elBottom >= 0;
switch (e.type) {
case 'wheel':
case 'onmousewheel':
case 'mousewheel':
case 'DOMMouseScroll':
var wheelDelta = e.wheelDelta ? e.wheelDelta : e.deltaY ? -e.deltaY : -e.detail,
wheelDir = (wheelDelta > 0) - (wheelDelta < 0),
wheelUp = wheelDir < 0, wheelDown = wheelDir > 0,
wheelAmount = 100 * wheelDir;
if (isTop && wheelDown) {
this.moveUp++;
this.moveDown = 0;
} else if (isBottom && wheelUp) {
this.moveUp = 0;
this.moveDown++;
} else {
this.moveUp = 0;
this.moveDown = 0;
}
var compulsivity = this.amplitude * Math.log(Math.max(this.moveUp, this.moveDown, 0.01) * wheelAmount* wheelDir);
this.monitor[e.type].track(wheelAmount, compulsivity);
break;
case 'scroll':
/* end of scroll event for iOS, start/end of scroll event for other browsers */
this.lastScrollY = currScrollY;
this.monitor[e.type].track(amountScrollY, 0);
break;
case 'touchstart':
var touches = [].slice.call(e.touches), i = touches.length;
while (i--) {
var touch = touches[i], id = touch.identifier;
this.startTouches[id] = touch;
this.moveTouches[id] = touch;
}
break;
case 'touchmove':
var touches = [].slice.call(e.touches), i = touches.length,
currTouches = {},
swipeUp = false, swipeDown = false,
currMoveY = 0, totalMoveY = 0;
while (i--) {
var touch = touches[i], id = touch.identifier;
currTouches[id] = touch;
if (id in this.moveTouches) {
currMoveY = this.moveTouches[id].screenY - touch.screenY;
}
if (id in this.startTouches) {
totalMoveY = this.startTouches[id].screenY - touch.screenY;
}
swipeUp = currMoveY > 0 || totalMoveY > 0;
swipeDown = currMoveY < 0 || totalMoveY < 0;
if (this.pullToRefresh && isTop && swipeDown && e.cancelable) {
e.preventDefault();
console.log('Reload prevented');
}
}
this.moveTouches = currTouches;
var moveDir = (totalMoveY > 0) - (totalMoveY < 0),
longSwipe = moveDir * totalMoveY > this.startThreshold,
shortSwipe = moveDir * totalMoveY > this.moveThreshold,
realSwipe = longSwipe || shortSwipe;
if (isTop && swipeDown) {
if (realSwipe) this.moveUp++;
this.moveDown = 0;
} else if (isBottom && swipeUp) {
this.moveUp = 0;
if (realSwipe) this.moveDown++;
} else {
this.moveUp = 0;
this.moveDown = 0;
}
var compulsivity = this.amplitude * Math.log(Math.max(this.moveUp, this.moveDown, 0.01) * moveDir * totalMoveY);
this.monitor[e.type].track(currMoveY, compulsivity);
break;
}
};
function Monitor(events) {
this.ctx = null;
this.cont = null;
this.events = events;
this.values = [];
this.average = 0;
this.lastDrawTime = 0;
this.inertiaDuration = 200;
return this;
}
Monitor.prototype.showOn = function (container) {
var cv = document.createElement('canvas');
this.ctx = cv.getContext('2d');
this.cont = document.getElementById(container);
cv.width = this.cont.offsetWidth;
cv.height = this.cont.offsetHeight;
cv.style.top = 0;
cv.style.left = 0;
cv.style.zIndex = -1;
cv.style.position = 'absolute';
cv.style.backgroundColor = '#000';
this.cont.appendChild(cv);
var self = this;
window.addEventListener('resize', function () {
var cv = self.ctx.canvas, cont = self.cont;
cv.width = cont.offsetWidth;
cv.height = cont.offsetHeight;
});
return this;
};
Monitor.prototype.track = function (value, average) {
this.average = average;
if (this.values.push(value) > this.ctx.canvas.width) this.values.shift();
if (value) this.lastDrawTime = new Date().getTime();
};
Monitor.prototype.draw = function () {
if (this.ctx) {
var cv = this.ctx.canvas, w = cv.width, h = cv.height;
var i = this.values.length, x = w | 0, y = (0.5 * h) | 0;
cv.style.backgroundColor = 'rgb(' + this.average + ', 0, 0)';
this.ctx.clearRect(0, 0, w, h);
this.ctx.strokeStyle = '#00ffff';
this.ctx.lineWidth = 1;
this.ctx.beginPath();
while (i--) {
x -= 4;
if (x < 0) break;
this.ctx.moveTo(x, y);
this.ctx.lineTo(x + 1, y);
this.ctx.lineTo(x + 1, y - this.values[i]);
}
this.ctx.stroke();
var elapsed = new Date().getTime() - this.lastDrawTime;
/* cool down */
this.average = this.average > 0 ? (this.average * 0.9) | 0 : 0;
if (elapsed > this.inertiaDuration) {
this.track(0, this.average);
}
}
var self = this;
setTimeout(function () {
self.draw();
}, 100);
};
Monitor.prototype.connectTo = function (tracker) {
var events = this.events.split(' '), i = events.length;
while (i--) {
tracker.monitor[events[i]] = this;
}
this.draw();
return this;
};
function loadSomeData(target) {
$.ajax({
url: 'https://jsonplaceholder.typicode.com/users',
method: 'GET',
crossDomain: true,
dataType: 'json',
success: function (users) {
var html = '', $ul = $(target).find('ul');
$.each(users, function (i, user) {
var item = '<li><a class="ui-alt-icon ui-nodisc-icon">';
item += '<h2>' + user.name + '</h2>';
item += '<p><strong>' + user.company.name + '</strong></p>';
item += '<p>' + user.address.zipcode + ', ' + user.address.city + '</p>';
item += '<p>' + user.phone + '</p>';
item += '<p>' + user.email + '</p>';
item += '<p class="ui-body-inherit ui-li-aside ui-li-count"><strong>' + user.id + '</strong></p>';
item += '</a></li>';
html += item;
});
$ul.append(html).listview('refresh');
},
});
}
$(document)
.on('pagecreate', '#page-list', function (e) {
$("[data-role='header'], [data-role='footer']").toolbar({ theme: 'a', position: 'fixed', tapToggle: false });
loadSomeData(e.target);
})
.on('pageshow', '#page-list', function (e, ui) {
var tracker = $.data(this, 'mobile-page', new Tracker(this));
new Monitor('touchstart touchmove').connectTo(tracker).showOn('header');
new Monitor('scroll wheel mousewheel DOMMouseScroll').connectTo(tracker).showOn('footer');
});
.ui-page {
touch-action: none;
}
h1, h2, h3, h4, h5, h6, p {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
/* JQM no frills */
.ui-btn,
.ui-title,
.ui-btn:hover,
.ui-btn:focus,
.ui-btn:active,
.ui-btn:visited {
text-shadow: none !important;
}
* {
-webkit-box-shadow: none !important;
-moz-box-shadow: none !important;
box-shadow: none !important;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Compulsivity</title>
<meta name="description" content="Compulsivity" />
<meta name="HandheldFriendly" content="True" />
<meta name="MobileOptimized" content="320" />
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, width=device-width, minimal-ui shrink-to-fit=no" />
<meta http-equiv="cleartype" content="on" />
<!-- Add to homescreen for Chrome on Android -->
<meta name="mobile-web-app-capable" content="yes" />
<!-- For iOS web apps. Delete if not needed. -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<meta name="apple-mobile-web-app-title" content="Compulsivity" />
<link rel="stylesheet" href="https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.css" />
<!--
<script type="application/javascript" src="lib/touch-emulator.js"></script>
<script> TouchEmulator(); </script>
-->
<script type="application/javascript" src="https://cdn.jsdelivr.net/npm/jquery#2.2.4/dist/jquery.min.js"></script>
<script type="application/javascript" src="https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js"></script>
</head>
<body>
<div id="header" data-role="header"><h4 style="color: #fff">Compulsivity</h4></div>
<div id="page-list" data-role="page">
<div data-role="content" role="main">
<ul data-role="listview" data-filter="true" data-inset="true"></ul>
</div>
</div>
<div id="footer" data-role="footer"><h4 style="color: #fff">Scroll</h4></div>
</body>
</html>
Among others, You need to be aware also of the pull-to-refresh and inertia (or momentum) of the smooth scroll behaviors.
Please, try to scroll or to swipe and look how the events are tracked: either the top bar or bottom bar will change color to display the user activity after reaching the bottom or the top respectively of the page.
JavaScript:
// get the button
var theBtn = document.getElementById('theBtn');
// get the box
var theBox = document.getElementById('theBox');
// add event to the button on click show/hide(toggle) the box
theBtn.addEventListener('click', () => {
theBox.classList.toggle('active');
});
// when scrolling on the box
theBox.onscroll = function(){
// get the top of the div
var theBoxTop = theBox.scrollTop;
if(theBoxTop <= 0){
// when it reaches 0 or less, hide the box. It'll toggle the class, since it's "show" will "hide"
theBox.classList.toggle('active');
}
};
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-size: 10px;
font-family: 'Arial', sans-serif;
height: 1500px;
}
html {
scroll-behavior: smooth;
}
ul {
list-style-type: none;
}
#theBox ul li {
border: 1px solid;
height: 100px;
}
#navbar-bottom {
height: 100px;
width: 100%;
background: rgb(90, 111, 143);
position: fixed;
bottom: 0;
left: 0;
right: 0;
box-shadow: 0 0 2px 2px rgba(90, 111, 143, 0.562);
display: flex;
justify-content: space-around;
align-items: center;
}
#theBox {
background-color: red;
height: 350px;
width: 100%;
position: fixed;
bottom: 0;
transform: translateY(100%);
transition: all 0.3s;
overflow-y: scroll;
}
#theBox.active{
transform: translateY(0);
}
.myBtns {
width: 50px;
height: 50px;
border-radius: 50%;
border: none;
position: relative;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
cursor: pointer;
}
.myBtns span {
height: 3px;
width: 30px;
background-color: black;
margin: 3px 0;
}
<main role="main">
<div id="theBox">
<ul>
<li><p>Text</p></li>
<li><p>Text</p></li>
<li><p>Text</p></li>
<li><p>Text</p></li>
<li><p>Text</p></li>
<li><p>Text</p></li>
<li><p>Text</p></li>
<li><p>Text</p></li>
<li><p>Text</p></li>
</ul>
</div>
<div id="navbar-bottom">
<button class="myBtns"></button>
<button class="myBtns" id="theBtn">
<span></span>
<span></span>
<span></span>
</button>
<button class="myBtns"></button>
</div>
</main>
jQuery:
// add event to the button on click show/hide(toggle) the box
$('#theBtn').click(function(){
$('#theBox').toggleClass('active');
});
// when scrolling on the box
$('#theBox').scroll(function () {
// get the top of the div
var theBoxTop = $('#theBox').scrollTop();
// when it reaches 0 or less, hide the box. It'll toggle the class, since it's "show" will "hide"
if(theBoxTop <= 0){
$('#theBox').toggleClass('active');
}
});
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-size: 10px;
font-family: 'Arial', sans-serif;
height: 1500px;
}
html {
scroll-behavior: smooth;
}
ul {
list-style-type: none;
}
#theBox ul li {
border: 1px solid;
height: 100px;
}
#navbar-bottom {
height: 100px;
width: 100%;
background: rgb(90, 111, 143);
position: fixed;
bottom: 0;
left: 0;
right: 0;
box-shadow: 0 0 2px 2px rgba(90, 111, 143, 0.562);
display: flex;
justify-content: space-around;
align-items: center;
}
#theBox {
background-color: red;
height: 350px;
width: 100%;
position: fixed;
bottom: 0;
transform: translateY(100%);
transition: all 0.3s;
overflow-y: scroll;
}
#theBox.active{
transform: translateY(0);
}
.myBtns {
width: 50px;
height: 50px;
border-radius: 50%;
border: none;
position: relative;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
cursor: pointer;
}
.myBtns span {
height: 3px;
width: 30px;
background-color: black;
margin: 3px 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<main role="main">
<div id="theBox">
<ul>
<li><p>Text</p></li>
<li><p>Text</p></li>
<li><p>Text</p></li>
<li><p>Text</p></li>
<li><p>Text</p></li>
<li><p>Text</p></li>
<li><p>Text</p></li>
<li><p>Text</p></li>
<li><p>Text</p></li>
</ul>
</div>
<div id="navbar-bottom">
<button class="myBtns"></button>
<button class="myBtns" id="theBtn">
<span></span>
<span></span>
<span></span>
</button>
<button class="myBtns"></button>
</div>
</main>
window.onscroll = function(ev) {
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
alert("you are at the bottom of the page");
}
};
Link to demo: http://jsfiddle.net/5xpoe4yg/
There are two solutions for this. One is for touch devices and second for devices using mouse.
Using Wheel event
If target is a mouse device, then we will use following method:
document.onwheel = event => ScrollAction(event);
For more info on wheel event, please visit this link.
Touch Devices
If target is a touch device then following method will be useful:
document.ontouchcancel = event => TouchInterrupt(event);
document.ontouchend = event => FingerRemoved(event);
document.ontouchmove = event => FingerDragged(event);
document.ontouchstart = event => FingerPlaced(event);
For more info on touch events, please visit this link.
I think your problem fully is solved by this solution.
Your specific question is solveable by listening to the wheel event, although the result is not terribly precise. The wheel event often fires before the scroll event so this example will sometimes log negative scroll value on the first scroll up from the bottom of the page:
const content = document.querySelector('.content');
for (let i = 0; i < 50; i++) {
const p = document.createElement('p');
p.textContent = 'Content';
content.append(p);
};
content.addEventListener('wheel', e => {
const atBottom = content.scrollHeight - content.scrollTop === content.clientHeight;
if (atBottom) console.log(e.deltaY);
});
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
body {
height: 100vh;
width: 100%;
}
.content {
overflow-y: scroll;
height: 100%;
}
<div class="content"></div>
As others have suggested, a better approach for your use case might instead be to have an overlay which you can trigger on click/touch and then scroll into view. One issue you might run into is that deeply nested scroll on web browsers can get real ugly real fast, without resorting to pure JS solutions which also have their own performance issues.
This is a popup that, when clicked on, opens and enables you to scroll. When it gets to the top of the page, it's header sticks.
var navbar = document.querySelector('.navbar'),
navheader = document.querySelector('.navheader');
// Toggle navbar
navheader.addEventListener('click', e => {
navbar.classList.toggle('open');
if (!navbar.classList.contains('open')) {
navbar.style.overflow = 'hidden';
document.body.style.overflow = '';
navbar.scrollTop = 0;
stickTop = false;
navbar.classList.remove('sticky');
navbar.style.top = '';
navbar.style.transition = '.2s';
setTimeout(() => {
navbar.style.transition = '';
}, 200);
}
else {
navbar.style.overflow = 'overlay';
navbar.style.transition = '.2s';
setTimeout(() => {
navbar.style.transition = '';
}, 200);
}
})
var prevtop = 0;
var stickTop = false;
// Add scroll listener
navbar.addEventListener('scroll', e => {
// If navbar is open
if (navbar.classList.contains('open')) {
if (!stickTop) {
navbar.style.top = navbar.getBoundingClientRect().top - navbar.scrollTop + 'px';
}
if ((window.innerHeight - navbar.getBoundingClientRect().bottom) >= 0) {
document.body.style.overflow = 'hidden';
navbar.style.overflow = 'auto';
navbar.style.top = 0;
navbar.classList.add('sticky');
stickTop = true;
}
if (navbar.scrollTop == 0) {
navbar.classList.remove('open');
navbar.style.overflow = 'hidden';
document.body.style.overflow = '';
stickTop = false;
navbar.classList.remove('sticky');
navbar.style.top = '';
navbar.style.transition = '.2s';
setTimeout(() => {
navbar.style.transition = '';
}, 200);
}
}
})
body {
font-family: sans-serif;
}
.navbar {
position: fixed;
top: calc(100vh - 50px);
height: 100vh;
left: 0;
width: 100%;
overflow: hidden;
}
.navbar.open {
top: 50vh;
}
.navcontent {
background: black;
width: 100%;
color: white;
}
.navcontent p {
margin: 0;
}
.navheader {
height: 50px;
width: 100%;
background: lightblue;
cursor: pointer;
top: 0;
position: sticky;
display: flex;
justify-content: center;
z-index: 1;
}
.navheader::before {
width: 50px;
height: 3px;
margin-top: 10px;
background: white;
border-radius: 3px;
content: '';
}
<div class="navbar">
<div class="navheader"></div>
<div class="navcontent"><p>S</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>E</p></div>
</div>
<div class="content">
<p>S</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>E</p>
</div>

How to create valid preloader with jQuery, which can preload mp3 file to browser cache?

I have a simple drawing spectrum on a website http://alldev.eu/html/mp3/index.phtml, which loads to browser cache a song and plays it after it's fully downloaded.
I've made a pre-loader for my site which displays a message and an image for 7 seconds while the song is being loaded. Unfortunately, it doesn't work in the way I'd like to since 7 seconds might not be enough time to load a song (for instance, a test song with 11 Megabytes)
How can I rebuild my site to pre-load all data? My current script is below:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"></meta>
<title>Shiny Toy Guns - Major Tom</title>
<script type="text/javascript" src="http://code.jquery.com/jquery-2.1.0.min.js"></script>
<script type="text/javascript">
(function($) {
$.fn.extend({
jqbar: function(options) {
var DefaultSpeedOfAnimation = 7000;
var settings = $.extend({
SpeedOfAnimation: DefaultSpeedOfAnimation,
LengthOfBar: 200,
barWidth: 10,
barColor: 'red',
label: ' ',
value: 100
}, options);
return this.each(function() {
var valueLabelHeight = 0;
var ContainerProgress = $(this);
ContainerProgress.addClass('jqbar horizontal').append('<div class="bar-label"></div><div class="bar-level-wrapper"><div class="bar-level"></div></div><div class="bar-percent"></div>');
var progressLabel = ContainerProgress.find('.bar-label').html(settings.label);
var progressBar = ContainerProgress.find('.bar-level').attr('data-value', settings.value);
var progressBarWrapper = ContainerProgress.find('.bar-level-wrapper');
progressBar.css({
height: settings.barWidth,
width: 0,
backgroundColor: settings.barColor
});
var valueLabel = ContainerProgress.find('.bar-percent');
valueLabel.html('0');
animateProgressBar(progressBar);
function animateProgressBar(progressBar) {
var level = parseInt(progressBar.attr('data-value'));
if (level > 100) {
level = 100;
alert('max value cannot exceed 100 percent');
}
var w = settings.LengthOfBar * level / 100;
progressBar.animate({
width: w
}, {
duration: DefaultSpeedOfAnimation,
step: function(currentWidth) {
var percent = parseInt(currentWidth / settings.LengthOfBar * 100);
if (isNaN(percent)) percent = 0;
ContainerProgress.find('.bar-percent').html(percent + '%');
}
});
}
});
}
});
})(jQuery);
</script>
<style>
body {
text-align: center;
background-color: black;
color: white;
}
footer {
float: center;
bottom: 0;
position: absolute;
color: #FFFFFF;
font: bold 1.2em/2.5 arial;
width: 99%;
}
.jqbar {
position: relative;
top: 100px;
}
.jqbar.horizontal div {
display: inline-block;
margin-left: 5px;
font-size: 11px;
font-weight: bold;
}
.jqbar.horizontal .bar-percent {
font-size: 11px;
font-weight: bold;
height: 20px;
margin-bottom: 5px;
}
#progressbar {
width: 400px;
height: 22px;
border: 1px solid #000;
background-color: #292929;
}
#progressbar div {
height: 100%;
color: #FFF;
text-align: center;
line-height: 22px;
width: 0;
background-color: #0099FF;
}
#preloader {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
color: #FFF;
z-index: 99;
height: 100%;
}
#status {
width: 200px;
height: 200px;
position: absolute;
left: 50%;
top: 50%;
background-color: #000;
background-repeat: no-repeat;
background-position: center;
margin: -100px 0 0 -100px;
}
</style>
</head>
<body onload="hide_preloader();">
<div id="preloader">
<div id="status">Wait for MP3 Load...
<br>
<br>
<br>
<img src="http://alldev.eu/html/images/Loader.gif" />
</div>
</div>
<footer>
<center>
<div id="bar-1"></div>
<canvas id="music" width="1024" height="250" style="display: block;"></canvas>
</center>
</footer>
<script type="text/javascript">
jQuery(window).load(function() {
jQuery("#status").delay(5000).fadeOut(2500);
jQuery("#preloader").delay(5000).fadeOut(2500);
jQuery("#bar-1").delay(5000).fadeOut(2500);
});
$(document).ready(function() {
$('#bar-1').jqbar({
SpeedOfAnimation: 7000,
label: 'Loading...',
value: 100,
barColor: '#FFF',
barWidth: 20
});
});
if (!window.AudioContext) {
if (!window.webkitAudioContext) {
alert('AudioContext not found!');
}
window.AudioContext = window.webkitAudioContext;
}
var context = new AudioContext();
var audioBuffer;
var sourceNode;
var analyser;
var javascriptNode;
var ctx = $("#music").get()[0].getContext("2d");
var gradient = ctx.createLinearGradient(0, 0, 0, 325);
gradient.addColorStop(1, '#FFFFFF');
gradient.addColorStop(0.75, '#00FFFF');
gradient.addColorStop(0.25, '#0000FF');
gradient.addColorStop(0, '#000000');
setupAudioNodes();
loadSound("http://alldev.eu/html/mp3/Shiny%20Toy%20Guns%20-%20Major%20Tom%20(Official%20Live).mp3");
function setupAudioNodes() {
javascriptNode = context.createScriptProcessor(2048, 1, 1);
javascriptNode.connect(context.destination);
analyser = context.createAnalyser();
analyser.smoothingTimeConstant = 0.75; //0.5;
analyser.fftSize = 512;
sourceNode = context.createBufferSource();
sourceNode.connect(analyser);
analyser.connect(javascriptNode);
sourceNode.connect(context.destination);
}
$(document).ready(function() {
$.ajax({
url: "soundfile.mp3",
success: function() {
$("#play_button").show();
}
});
});
function loadSound(url) {
var request = new XMLHttpRequest();
request.open('GET', url, true);
request.responseType = 'arraybuffer';
request.onload = function() {
context.decodeAudioData(request.response, function(buffer) {
playSound(buffer);
}, onError);
}
request.send();
}
function playSound(buffer) {
sourceNode.buffer = buffer;
sourceNode.start(0);
}
function onError(e) {
console.log(e);
}
javascriptNode.onaudioprocess = function() {
var array = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(array);
ctx.clearRect(0, 0, 1024, 325);
ctx.fillStyle = gradient;
drawSpectrum(array);
}
function drawSpectrum(array) {
for (var i = 0; i < (array.length); i++) {
var value = array[i];
ctx.fillRect(i * 5, 325 - value, 3, 325);
}
};
</script>
</body>
</html>
you can give preloadJS a try.
http://www.createjs.com/#!/PreloadJS
here's some code from their documentation to get you started:
var queue = new createjs.LoadQueue();
queue.installPlugin(createjs.Sound);
queue.on("complete", handleComplete, this);
queue.loadFile({id:"sound", src:"http://path/to/sound.mp3"});
queue.loadManifest([
{id: "myImage", src:"path/to/myImage.jpg"}
]);
function handleComplete() {
createjs.Sound.play("sound");
var image = queue.getResult("myImage");
document.body.appendChild(image);
}
Check for JWPlayer API, you can have access to a method getBuffer(), which returns the current buffered state for your audio file : http://www.longtailvideo.com/support/jw-player/28851/javascript-api-reference/
But this requires to use JWplayer to play / handle your audio file, as it's not included in the audio object : http://www.w3schools.com/tags/av_prop_buffered.asp
EDIT :
I took some time to make it work, see the fiddle :) : http://jsfiddle.net/uKZ8N/
Basically, I set an interval of 0,5s which check the getBuffer() value.

Display-inline-block with z-index and jquery

Good morning everyone,
I seem to be having a slight problem. I want a div to overlay another div(i.e. be on top) but zIndex is not working. I suspect the cause is the display: inline-block but I need to keep it so that the webpage is displayed properly. How do I make the div overlay the other one?
Here is the jsfiddle explaining the problem:
http://jsfiddle.net/Yf7zD/
Or the code right here:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Untitled Document</title>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.0/jquery.min.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.9/jquery-ui.min.js"></script>
<script type="text/javascript" src="javascript/cookies.js"></script>
<style>
#gameTable {
font-size: 0;
width: 840px;
height: 240px;
margin-top: 20px;
margin-left: 78px;
position: relative;
}
.iamdroppable {
display: inline-block;
margin: 0;
padding: 0;
border: 3px solid #FFF;
}
</style>
</head>
<body style="background-color: black;">
<div id="container" style="position:relative; border: solid 3px red;">
<div id="gameTable"><p>GameTable</p>
</div>
</div>
</body>
<script>
$(document).ready(function(e) {
var myId;
for(vertical = 0; vertical < 3; vertical++) {
for(horizontal = 1; horizontal <= 12; horizontal++) { //Outer Numbers
if(vertical == 0)
myId = horizontal * 3;
else if(vertical == 1)
myId = horizontal * 3 - 1;
else
myId = horizontal * 3 - 2;
$('<div>', {//Normal numbers
class: 'iamdroppable',
id: '' + myId,
width: '62px',
height: '78px',
}).appendTo('#gameTable');
}
}
$('<div>', {//Quads
class: 'iamdroppable',
id: '1000',
top: '-100px',
width: '100px',
height: '200px',
zIndex: '1000',
position: 'absolute',
}).appendTo('#container');
});
</script>
</html>
Thanks!
var div = $('<div>', {//Quads
class: 'iamdroppable',
id: '1000',
top: '-100px',
width: '100px',
height: '200px',
zIndex: '1000',
position: 'absolute',
}).appendTo('#container');
});
$('body').append(div);
You were adding the CSS styles as attributes, they should all be within the style property:
$(document).ready(function (e) {
var myId;
for (vertical = 0; vertical < 3; vertical++) {
for (horizontal = 1; horizontal <= 12; horizontal++) { //Outer Numbers
if (vertical === 0) myId = horizontal * 3;
else if (vertical == 1) myId = horizontal * 3 - 1;
else myId = horizontal * 3 - 2;
$('<div>', { //Normal numbers
class: 'iamdroppable',
id: '' + myId,
style: 'width:62px;height:78px;'
}).appendTo('#gameTable');
}
}
$('<div>', { //Quads
class: 'iamdroppable',
id: '1000',
style: 'top:-100px;width:100px;height:200px;z-index:1000;position:absolute;'
}).appendTo('#container');
});
On another note, where you're doing this comparison: if (vertical == 0) you should use three === like this: if (vertical === 0)

Categories

Resources