I have tried to create single scroll and move to next section, I have using javascript, It is not working fine, The window top distance not giving properly, I need to div fullscreen moved to next screen, Please without jquery, Please help
if (window.addEventListener) {window.addEventListener('DOMMouseScroll', wheel, false);
window.onmousewheel = document.onmousewheel = wheel;}
function wheel(event) {
var delta = 0;
if (event.wheelDelta) delta = (event.wheelDelta)/120 ;
else if (event.detail) delta = -(event.detail)/3;
handle(delta);
if (event.preventDefault) event.preventDefault();
event.returnValue = false;
}
function handle(sentido) {
var inicial = document.body.scrollTop;
var time = 500;
var distance = 900;
animate({
delay: 0,
duration: time,
delta: function(p) {return p;},
step: function(delta) {
window.scrollTo(0, inicial-distance*delta*sentido);
}
});
}
function animate(opts) {
var start = new Date();
var id = setInterval(function() {
var timePassed = new Date() - start;
var progress = (timePassed / opts.duration);
if (progress > 1) {progress = 1;}
var delta = opts.delta(progress);
opts.step(delta);
if (progress == 1) {clearInterval(id);}}, opts.delay || 10);
}
body{
width: 100%;
height: 100%;
margin:0;
padding:0;
}
.wrapper{
width: 100%;
height: 100%;
position: absolute;
}
section{
width: 100%;
height: 100%;
}
.pg1{
background: green;
}
.pg2{
background: blue;
}
.pg3{
background: yellow;
}
<div class="wrapper" id="myDiv">
<section class="pg1" id="sec1"></section>
<section class="pg2"></section>
<section class="pg3"></section>
</div>
Return the height of the element and store it in distance variable, instead of giving it a static 900.
From this:
var distance = 900;
To this:
var distance = document.getElementById('sec1').clientHeight;
Related
I have a large background image and some much smaller images for the user to drag around on the background. I need this to be efficient in terms of performance, so i'm trying to avoid libraries. I'm fine with drag 'n' drop if it work's well, but im trying to get drag.
Im pretty much trying to do this. But after 8 years there must be a cleaner way to do this right?
I currently have a drag 'n' drop system that almost works, but when i drop the smaller images, they are just a little off and it's very annoying. Is there a way to fix my code, or do i need to take a whole different approach?
This is my code so far:
var draggedPoint;
function dragStart(event) {
draggedPoint = event.target; // my global var
}
function drop(event) {
event.preventDefault();
let xDiff = draggedPoint.x - event.pageX;
let yDiff = draggedPoint.y - event.pageY;
let left = draggedPoint.style.marginLeft; // get margins
let top = draggedPoint.style.marginTop;
let leftNum = Number(left.substring(0, left.length - 2)); // cut off px from the end
let topNum = Number(top.substring(0, top.length - 2));
let newLeft = leftNum - xDiff + "px" // count new margins and put px back to the end
let newTop = topNum - yDiff + "px"
draggedPoint.style.marginLeft = newLeft;
draggedPoint.style.marginTop = newTop;
}
function allowDrop(event) {
event.preventDefault();
}
let imgs = [
"https://upload.wikimedia.org/wikipedia/commons/6/67/Orange_juice_1_edit1.jpg",
"https://upload.wikimedia.org/wikipedia/commons/f/ff/Solid_blue.svg",
"https://upload.wikimedia.org/wikipedia/commons/b/b4/Litoria_infrafrenata_-_Julatten.jpg"
]
/* my smaller images: */
for (let i = 0; i < 6; i++) {
let sensor = document.createElement("img");
sensor.src = imgs[i % imgs.length];
sensor.alt = i;
sensor.draggable = true;
sensor.classList.add("sensor");
sensor.style.marginLeft = `${Math.floor(Math.random() * 900)}px`
sensor.style.marginTop = `${Math.floor(Math.random() * 500)}px`
sensor.onclick = function() {
sensorClick(logs[i].id)
};
sensor.addEventListener("dragstart", dragStart, null);
let parent = document.getElementsByClassName("map")[0];
parent.appendChild(sensor);
}
<!-- my html: -->
<style>
.map {
width: 900px;
height: 500px;
align-content: center;
margin: 150px auto 150px auto;
}
.map .base {
position: absolute;
width: inherit;
height: inherit;
}
.map .sensor {
position: absolute;
width: 50px;
height: 50px;
}
</style>
<div class="map" onDrop="drop(event)" ondragover="allowDrop(event)">
<img src='https://upload.wikimedia.org/wikipedia/commons/f/f7/Plan-Oum-el-Awamid.jpg' alt="pohja" class="base" draggable="false">
<div>
With the answers from here and some time i was able to get a smooth drag and click with pure js.
Here is a JSFiddle to see it in action.
let maxLeft;
let maxTop;
const minLeft = 0;
const minTop = 0;
let timeDelta;
let imgs = [
"https://upload.wikimedia.org/wikipedia/commons/6/67/Orange_juice_1_edit1.jpg",
"https://upload.wikimedia.org/wikipedia/commons/f/ff/Solid_blue.svg",
"https://upload.wikimedia.org/wikipedia/commons/b/b4/Litoria_infrafrenata_-_Julatten.jpg"
]
var originalX;
var originalY;
window.onload = function() {
document.onmousedown = startDrag;
document.onmouseup = stopDrag;
}
function sensorClick () {
if (Date.now() - timeDelta < 150) { // check that we didn't drag
createPopup(this);
}
}
// create a popup when we click
function createPopup(parent) {
let p = document.getElementById("popup");
if (p) {
p.parentNode.removeChild(p);
}
let popup = document.createElement("div");
popup.id = "popup";
popup.className = "popup";
popup.style.top = parent.y - 110 + "px";
popup.style.left = parent.x - 75 + "px";
let text = document.createElement("span");
text.textContent = parent.id;
popup.appendChild(text);
var map = document.getElementsByClassName("map")[0];
map.appendChild(popup);
}
// when our base is loaded
function baseOnLoad() {
var map = document.getElementsByClassName("map")[0];
let base = document.getElementsByClassName("base")[0];
maxLeft = base.width - 50;
maxTop = base.height - 50;
/* my smaller images: */
for (let i = 0; i < 6; i++) {
let sensor = document.createElement("img");
sensor.src = imgs[i % imgs.length];
sensor.alt = i;
sensor.id = i;
sensor.draggable = true;
sensor.classList.add("sensor");
sensor.classList.add("dragme");
sensor.style.left = `${Math.floor(Math.random() * 900)}px`
sensor.style.top = `${Math.floor(Math.random() * 500)}px`
sensor.onclick = sensorClick;
let parent = document.getElementsByClassName("map")[0];
parent.appendChild(sensor);
}
}
function startDrag(e) {
timeDelta = Date.now(); // get current millis
// determine event object
if (!e) var e = window.event;
// prevent default event
if(e.preventDefault) e.preventDefault();
// IE uses srcElement, others use target
targ = e.target ? e.target : e.srcElement;
originalX = targ.style.left;
originalY = targ.style.top;
// check that this is a draggable element
if (!targ.classList.contains('dragme')) return;
// calculate event X, Y coordinates
offsetX = e.clientX;
offsetY = e.clientY;
// calculate integer values for top and left properties
coordX = parseInt(targ.style.left);
coordY = parseInt(targ.style.top);
drag = true;
document.onmousemove = dragDiv; // move div element
return false; // prevent default event
}
function dragDiv(e) {
if (!drag) return;
if (!e) var e = window.event;
// move div element and check for borders
let newLeft = coordX + e.clientX - offsetX;
if (newLeft < maxLeft && newLeft > minLeft) targ.style.left = newLeft + 'px'
let newTop = coordY + e.clientY - offsetY;
if (newTop < maxTop && newTop > minTop) targ.style.top = newTop + 'px'
return false; // prevent default event
}
function stopDrag() {
if (typeof drag == "undefined") return;
if (drag) {
if (Date.now() - timeDelta > 150) { // we dragged
let p = document.getElementById("popup");
if (p) {
p.parentNode.removeChild(p);
}
} else {
targ.style.left = originalX;
targ.style.top = originalY;
}
}
drag = false;
}
.map {
width: 900px;
height: 500px;
margin: 50px
position: relative;
}
.map .base {
position: absolute;
width: inherit;
height: inherit;
}
.map .sensor {
display: inline-block;
position: absolute;
width: 50px;
height: 50px;
}
.dragme {
cursor: move;
left: 0px;
top: 0px;
}
.popup {
position: absolute;
display: inline-block;
width: 200px;
height: 100px;
background-color: #9FC990;
border-radius: 10%;
}
.popup::after {
content: "";
position: absolute;
top: 100%;
left: 50%;
margin-left: -10px;
border-width: 10px;
border-style: solid;
border-color: #9FC990 transparent transparent transparent;
}
.popup span {
width: 90%;
margin: 10px;
display: inline-block;
text-align: center;
}
<div class="map" width="950px" height="500px">
<img src='https://upload.wikimedia.org/wikipedia/commons/f/f7/Plan-Oum-el-Awamid.jpg' alt="pohja" class="base" draggable="false" onload="baseOnLoad()">
<div>
The following is a link which shows a nice example of playing a local video in a browser:
http://jsfiddle.net/dsbonev/cCCZ2/
<h1>HTML5 local video file player example</h1>
<div id="message"></div>
<input type="file" accept="video/*"/>
<video controls autoplay></video>
However, on top of this, I would like to allow the user to create a "trailer clip" of a particular segment of their video. In this, I would like to have some sort of adjustable playhead such as the following:
Here is a site that does this exact same thing: https://www.flexclip.com/editor/app?ratio=landscape. Any insight into how I would go about building something like this that plays the local file and allows me to select a segment of it?
There's a few different things, though the particular question seems to be how to draw a preview bar of a local video.
Local Files
From the example in your question, you can see it's not too different working with a local file than with a remote file; you can create an object URL of the local file via URL.createObjectURL and then work with it as you would a normal video link.
Drawing the Preview Bar
Drawing the preview bar should consist of seeking to the appropriate time in the video, and then drawing that frame onto a canvas. You can seek to a specific point by setting the .currentTime property of the video and waiting for the seeked event. You can then use the canvasContext.drawImage method to draw an image using the video element.
I'd also recommend using a video element that isn't attached to the DOM for this, for a better user experience that doesn't have an onscreen video jumping to different times.
It could look something like this:
function goToTime(video, time) {
return new Promise ((resolve) => {
function cb () {
video.removeEventListener('seeked', cb);
resolve();
}
video.addEventListener('seeked', cb);
video.currentTime = time;
});
}
async function drawPreviewBar(video) {
const { duration, videoWidth, videoHeight } = video;
const previewBarFrameWidth = previewBarHeight * videoWidth / videoHeight;
const previewBarFrames = previewBarWidth / previewBarFrameWidth;
for (let i = 0; i < previewBarFrames; i++) {
await goToTime(video, i * duration * previewBarFrameWidth / previewBarWidth);
previewBarContext.drawImage(video, 0, 0,
videoWidth, videoHeight,
previewBarFrameWidth * i, 0,
previewBarFrameWidth, previewBarHeight
);
}
}
Resizing / Moving the Selected Clip
If you want to create a UI to resize or move around the clip, you can use standard drag handlers.
Example
Here's a basic example:
const previewBarWidth = 600;
const previewBarHeight = 40;
const videoElement = document.getElementById('video');
const inputElement = document.getElementById('file-picker');
const invisibleVideo = document.createElement('video');
const previewBarCanvas = document.getElementById('preview-bar-canvas');
previewBarCanvas.width = previewBarWidth;
previewBarCanvas.height = previewBarHeight;
const selector = document.getElementById('selector');
const previewBarContext = previewBarCanvas.getContext('2d');
const topHandle = document.getElementById('selector-top-handle');
const leftHandle = document.getElementById('selector-left-handle');
const rightHandle = document.getElementById('selector-right-handle');
const leftMask = document.getElementById('selector-left-mask');
const rightMask = document.getElementById('selector-right-mask');
var selectorLeft = 0, selectorWidth = previewBarWidth;
var minimumPreviewBarWidth = 80; // may want to dynamically change this based on video duration
var videoLoaded = false;
function goToTime(video, time) {
return new Promise ((resolve) => {
function cb () {
video.removeEventListener('seeked', cb);
resolve();
}
video.addEventListener('seeked', cb);
video.currentTime = time;
});
}
async function drawPreviewBar(video) {
const { duration, videoWidth, videoHeight } = video;
const previewBarFrameWidth = previewBarHeight * videoWidth / videoHeight;
const previewBarFrames = previewBarWidth / previewBarFrameWidth;
for (let i = 0; i < previewBarFrames; i++) {
await goToTime(video, i * duration * previewBarFrameWidth / previewBarWidth);
previewBarContext.drawImage(video, 0, 0, videoWidth, videoHeight, previewBarFrameWidth * i, 0, previewBarFrameWidth, previewBarHeight);
}
}
function loadVideo(file) {
var src = URL.createObjectURL(file);
var loaded = new Promise(function (resolve) {
invisibleVideo.addEventListener('loadedmetadata', resolve);
}).then(function () {
videoLoaded = true;
document.body.classList.add('loaded');
return drawPreviewBar(invisibleVideo);
});
videoElement.src = src;
invisibleVideo.src = src;
return loaded;
}
function updateSelectorBar() {
selector.style.width = selectorWidth + 'px';
selector.style.left = selectorLeft + 'px';
leftMask.style.width = selectorLeft + 'px';
rightMask.style.left = (selectorLeft + selectorWidth) + 'px';
rightMask.style.width = (previewBarWidth - selectorWidth - selectorLeft) + 'px';
}
function selectorUpdated() {
if (!videoLoaded) {
return;
}
var startFraction = selectorLeft / previewBarWidth;
var durationFraction = selectorWidth / previewBarWidth;
var startTime = startFraction * invisibleVideo.duration;
var duration = durationFraction * invisibleVideo.duration;
var endTime = startTime + duration;
// do something with startTime, endTime, and duration, maybe;
videoElement.currentTime = startTime;
}
function addDragHandler (event, cb, ecb) {
var startX = event.clientX;
function dragged(e) {
cb(e.clientX - startX);
}
window.addEventListener('mousemove', dragged);
window.addEventListener('mouseup', function ended() {
window.removeEventListener('mousemove', dragged);
window.removeEventListener('mouseup', ended);
ecb();
});
}
updateSelectorBar();
topHandle.addEventListener('mousedown', function (e) {
var startLeft = selectorLeft;
addDragHandler(e, function (dx) {
selectorLeft = Math.max(0, Math.min(previewBarWidth - selectorWidth, startLeft + dx));
updateSelectorBar();
}, selectorUpdated);
});
leftHandle.addEventListener('mousedown', function (e) {
var startLeft = selectorLeft;
var startWidth = selectorWidth;
addDragHandler(e, function (dx) {
selectorLeft = Math.max(0, Math.min(selectorLeft + selectorWidth - minimumPreviewBarWidth, startLeft + dx));
selectorWidth = (startWidth + startLeft - selectorLeft);
updateSelectorBar();
}, selectorUpdated);
});
rightHandle.addEventListener('mousedown', function (e) {
var startWidth = selectorWidth;
addDragHandler(e, function (dx) {
selectorWidth = Math.max(minimumPreviewBarWidth, Math.min(previewBarWidth - selectorLeft, startWidth + dx));
updateSelectorBar();
}, selectorUpdated);
});
var pendingLoad = Promise.resolve();
inputElement.addEventListener('change', function () {
let file = inputElement.files[0];
pendingLoad = pendingLoad.then(function () {
return loadVideo(file)
});
});
#video {
width: 100%;
height: auto;
}
#preview-bar {
position: relative;
margin-top: 10px;
}
.canvas-container {
position: relative;
left: 5px;
}
#preview-bar-canvas {
position: relative;
top: 2px;
}
#selector {
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 100%;
border: 2px solid orange;
border-left-width: 5px;
border-right-width: 5px;
border-radius: 3px;
overflow: show;
opacity: 0;
transition: opacity 1s;
pointer-events: none;
}
.loaded #selector{
opacity: 1;
pointer-events: initial;
}
#selector-top-handle {
position: absolute;
top: 0;
height: 10px;
width: 100%;
max-width: 30px;
left: 50%;
transform: translate(-50%,-100%);
background: darkgreen;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
cursor: move;
}
.resize-handle {
position: absolute;
top: 0;
bottom: 0;
width: 5px;
cursor: ew-resize;
background: darkgreen;
opacity: 0.75;
transition: opacity 1s;
}
.resize-handle:hover {
opacity: 1;
}
.preview-mask {
position: absolute;
background: black;
opacity: 0.6;
top: 0;
bottom: 0;
}
#selector-left-mask {
left: 0;
}
#selector-left-handle {
left: -5px;
}
#selector-right-handle {
right: -5px;
}
<input type="file" id="file-picker" />
<video id="video"></video>
<div id="preview-bar">
<div class="canvas-container">
<canvas id="preview-bar-canvas"></canvas>
<div id="selector-left-mask" class="preview-mask"></div>
<div id="selector-right-mask" class="preview-mask"></div>
</div>
<div id ="selector">
<div id="selector-top-handle"></div>
<div id="selector-left-handle" class="resize-handle"></div>
<div id="selector-right-handle" class="resize-handle"></div>
</div>
</div>
Here is some infomation I found online about this question:
(all of the lines are commented fyi)
https://gist.github.com/simonhaenisch/116010ed657f6b257246464e33719613
I am trying to develop a very simple game where the ship (red box) will move left-right when user clicks on playground.
There are some moving walls (black boxes) as obstacles that the ship should avoid colliding with.
If any collision happens, the walls will stop moving and a text will be printed out in console.
I have succeeded to get this as close as I can. But its working sometime, not always. You can see it in the code below, try to collide with wall. Sometime it will stop them and print text, sometime it will just ignore the collision as if nothing happens.
I have no clue why this is happening.
Here is the code.
$('document').ready(function() {
var $totalHeight = $('.inner').height(); //of walls
var $maxHeight = Math.ceil(Math.ceil($totalHeight / 3) - (Math.ceil($totalHeight / 3) * 30) / 100); //30% of total wall height
$('.wall').each(function(i, obj) {
$(this).height($maxHeight);
$('.wall.four').css({
'height': $wallGap
});
})
var $wallGap = Math.ceil($totalHeight / 3) - $maxHeight;
var $wallOneTop = 0;
var $wallTwoTop = $maxHeight + $wallGap;
var $wallThreeTop = ($maxHeight * 2) + ($wallGap * 2);
var $wallFourTop = -$('.wall.four').height() - $wallGap;
$('.wall.one').css({
'top': $wallOneTop
});
$('.wall.two').css({
'top': $wallTwoTop
});
$('.wall.three').css({
'top': $wallThreeTop
});
$('.wall.four').css({
'top': $wallFourTop
});
function moveWall(wallObj) {
var $currentTop = wallObj.position().top;
var $limitTop = $('.inner').height();
if ($currentTop >= $limitTop) {
var $rand = Math.floor(Math.random() * ($maxHeight - $wallGap + 1) + $wallGap);
wallObj.height($rand);
var $top = -(wallObj.height());
} else {
var $top = (wallObj.position().top) + 5;
}
var $collide = checkCollision(wallObj);
wallObj.css({
'top': $top
});
return $collide;
}
var $wallTimer = setInterval(function() {
$('.wall').each(function(i, obj) {
var $status = moveWall($(this));
if ($status == true) {
clearInterval($wallTimer);
}
})
}, 40);
function checkCollision(wallObj) {
var $ship = $('.ship');
var $shipWidth = $ship.width();
var $shipHeight = $ship.height();
var $shipLeft = $ship.position().left;
var $shipRight = $shipLeft + $shipWidth;
var $shipTop = $ship.position().top;
var $shipBottom = $shipTop + $shipHeight;
var $wall = wallObj;
var $wallWidth = wallObj.width();
var $wallHeight = wallObj.height();
var $wallLeft = wallObj.position().left;
var $wallRight = $wallLeft + $wallWidth;
var $wallTop = wallObj.position().top;
var $wallBottom = $wallTop + $wallHeight;
if (
$shipLeft >= $wallRight ||
$shipRight <= $wallLeft ||
$shipTop >= $wallBottom ||
$shipBottom <= $wallTop
) {
return false;
} else {
console.log("dhumm!");
return true;
}
}
$('.outer .inner').click(function() {
var $ship;
$ship = $('.ship');
$shipLeft = $ship.position().left;
$shipRight = $shipLeft + $ship.width();
$inner = $('.inner');
$innerLeft = $inner.position().left;
$innerRight = $innerLeft + $inner.width();
if (($shipLeft < $inner.width() - $ship.width())) {
$ship.animate({
"left": $inner.width() - $ship.width()
}, 500, "linear");
} else if (($shipRight >= $inner.width())) {
$ship.animate({
"left": '0'
}, 500, "linear");
}
});
});
.outer {
background: #fff;
border: 20px solid #efefef;
width: 400px;
height: 600px;
display: inline-block;
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
overflow: hidden;
}
.outer .inner {
background: #fff;
height: 100%;
width: 100%;
margin: auto;
position: relative;
overflow: hidden;
}
.outer .inner .wall {
width: 5px;
position: absolute;
left: 50%;
transform: translateX(-50%);
background: #000;
}
.outer .inner .ship {
width: 15px;
height: 15px;
background: red;
position: absolute;
top: 50%;
transform: translateY(-50%);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="outer">
<div class="inner">
<div class="wall one"></div>
<div class="wall two"></div>
<div class="wall three"></div>
<div class="wall four"></div>
<div class="ship"></div>
</div>
</div>
As freefomn-m already said.
Check for collision in the animation cycle of the ship, not the walls.
For this I use the second type of parameters for jQuery's .animate method
.animate( properties, options )
I use the "progress" option to check the collision in every movement cycle of the ship.
console.clear();
$('document').ready(function() {
var collided = false;
var collidedWith = null;
var $ship = $('.ship');
var $walls = $('.wall')
var $totalHeight = $('.inner').height(); //of walls
var $maxHeight = Math.ceil(Math.ceil($totalHeight / 3) - (Math.ceil($totalHeight / 3) * 30) / 100); //30% of total wall height
$('.wall').each(function(i, obj) {
$(this).height($maxHeight);
$('.wall.four').css({
'height': $wallGap
});
})
var $wallGap = Math.ceil($totalHeight / 3) - $maxHeight;
var $wallOneTop = 0;
var $wallTwoTop = $maxHeight + $wallGap;
var $wallThreeTop = ($maxHeight * 2) + ($wallGap * 2);
var $wallFourTop = -$('.wall.four').height() - $wallGap;
$('.wall.one').css({
'top': $wallOneTop
});
$('.wall.two').css({
'top': $wallTwoTop
});
$('.wall.three').css({
'top': $wallThreeTop
});
$('.wall.four').css({
'top': $wallFourTop
});
function moveWall(wallObj) {
var $currentTop = wallObj.position().top;
var $limitTop = $('.inner').height();
if ($currentTop >= $limitTop) {
var $rand = Math.floor(Math.random() * ($maxHeight - $wallGap + 1) + $wallGap);
wallObj.height($rand);
var $top = -(wallObj.height());
} else {
var $top = (wallObj.position().top) + 5;
}
// var $collide = checkCollision(wallObj);
wallObj.css({
'top': $top
});
// return $collide;
}
var $wallTimer = setInterval(function() {
$walls.each(function(i, obj) {
moveWall($(this));
if (collided) {
clearInterval($wallTimer);
}
})
}, 40);
function checkCollision() {
var $shipWidth = $ship.width();
var $shipHeight = $ship.height();
var $shipLeft = $ship.position().left;
var $shipRight = $shipLeft + $shipWidth;
var $shipTop = $ship.position().top;
var $shipBottom = $shipTop + $shipHeight;
$('.wall').each(function(i) {
var $wall = $(this);
var $wallWidth = $wall.width();
var $wallHeight = $wall.height();
var $wallLeft = $wall.position().left;
var $wallRight = $wallLeft + $wallWidth;
var $wallTop = $wall.position().top;
var $wallBottom = $wallTop + $wallHeight;
if (
$shipLeft < $wallRight &&
$shipRight > $wallLeft &&
$shipTop < $wallBottom &&
$shipBottom > $wallTop
) {
console.log("dhumm!");
collided = true;
collidedWith = $wall
$wall.addClass('crashed')
$ship.addClass('crashed')
$ship.stop();
return false;
}
})
}
$('.outer .inner').click(function() {
var $ship;
$ship = $('.ship');
$shipLeft = $ship.position().left;
$shipRight = $shipLeft + $ship.width();
$inner = $('.inner');
$innerLeft = $inner.position().left;
$innerRight = $innerLeft + $inner.width();
if (($shipLeft < $inner.width() - $ship.width())) {
$ship.animate({
"left": $inner.width() - $ship.width()
}, {
"duration": 500,
"easing": "linear",
"progress": checkCollision,
});
} else if (($shipRight >= $inner.width())) {
$ship.animate({
"left": '0'
}, {
"duration": 500,
"easing": "linear",
"progress": checkCollision,
});
}
});
});
.outer {
background: #fff;
border: 20px solid #efefef;
width: 400px;
height: 600px;
display: inline-block;
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
overflow: hidden;
}
.outer .inner {
background: #fff;
height: 100%;
width: 100%;
margin: auto;
position: relative;
overflow: hidden;
}
.outer .inner .wall {
width: 5px;
position: absolute;
left: 50%;
transform: translateX(-50%);
background: #000;
}
.outer .inner .wall.crashed {
background: red;
}
.outer .inner .ship {
width: 15px;
height: 15px;
background: orange;
position: absolute;
top: 50%;
transform: translateY(-50%);
}
.outer .inner .ship.crashed {
background: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="outer">
<div class="inner">
<div class="wall one"></div>
<div class="wall two"></div>
<div class="wall three"></div>
<div class="wall four"></div>
<div class="ship"></div>
</div>
</div>
As a recommendation how I would do this from scratch.
Use an update cycle that is called by either setInterval or setTimeout, or even better with requestAnimationFrame. The updatecycle would be responsible for the time progress and orchestrate the different objects. The structure would be like this.
jQuery(function($) { // same as $('document').ready()
var ship = ...;
var boundaries = ...;
var walls = ...;
var clickEvents = [];
document.addEventListener('click', function(e) {clickEvents.push(e)})
var handleEvents = function() {}
var setupWalls = function () {}
var setupShip= function () {}
var moveWalls = function () {}
var moveShip = function () {}
var checkCollision() {}
var setup = function() {
setupWalls();
setupShip();
// set the initial positions of the ships and the walls
}
var update = function() {
handleEvents();
moveWalls();
moveShips();
var collided = checkCollision();
if (!collided) {
setTimeout(update, 30);
}
}
setup();
update();
})
See the following snippet of code.
It creates a loader on click of a button. But the animation is not smooth.
I have recently read about requestAnimationFrame function which can do this job. But how can I use it to replace setInterval altogether since there is no way to specify time in requestAnimationFrame function.
Can it be used in conjunction with setInterval ?
let idx = 1;
const timetoEnd = 5000;
function ProgressBar(width){
this.width = width || 0;
this.id = `pBar-${idx++}`;
this.create = () => {
let pBar = document.createElement('div');
pBar.id = this.id;
pBar.className = `p-bar`;
pBar.innerHTML = `<div class="loader"></div>`;
return pBar;
};
this.animator = () => {
let element = document.querySelector(`#${(this.id)} div`);
if(this.width < 100){
this.width++;
element.style.width = `${this.width}%`;
} else {
clearInterval(this.interval);
}
};
this.animate = () => {
this.interval = setInterval(this.animator, timetoEnd/100);
}
}
function addLoader (){
let bar1 = new ProgressBar(40);
let container = document.querySelector("#container");
container.appendChild(bar1.create());
bar1.animate();
}
.p-bar{
width: 400px;
height: 20px;
background: 1px solid #ccc;
margin: 10px;
overflow:hidden;
border-radius: 4px;
}
.p-bar .loader{
width: 0;
background: #1565C0;
height: 100%;
}
<input type="button" value="Add loader" onclick="addLoader()" />
<div id="container"></div>
You are right, requestAnimationFrame is the recommended way to avoid UI jam when doing animation.
You can remember the absolute starting time at start instead of trying to do this at each frame. Then it's just a matter of computing a width based on the delta time between start and current time.
Also, document.querySelector is considered a relatively "heavy" operation so I added this.element to avoid doing it at each frame.
Here is how to new width is computed: ((100 - this.startWidth) / timetoEnd) * deltaT + this.startWidth
100 - this.startWidth is the total amount of width we have to animate
(100 - this.startWidth) / timetoEnd is how much width each second must add to (1)
((100 - this.startWidth) / timetoEnd) * deltaT is how much width we have to add to (1)
We just have to shift the whole thing this.startWidth px to have the frame's width
Also notice that some of this computation is constant and do not have to be computed on each frame, which I left as an exercise :)
Here is your slightly adapted code:
let idx = 1;
const timetoEnd = 5000;
function ProgressBar(startWidth){
this.startWidth = startWidth || 0;
this.id = `pBar-${idx++}`;
this.create = () => {
let pBar = document.createElement('div');
pBar.id = this.id;
pBar.className = `p-bar`;
pBar.innerHTML = `<div class="loader"></div>`;
return pBar;
};
this.animator = () => {
const deltaT = Math.min(new Date().getTime() - this.start, timetoEnd);
if(deltaT < timetoEnd){
const width = ((100 - this.startWidth) / timetoEnd) * deltaT + this.startWidth;
this.element.style.width = `${width}%`;
requestAnimationFrame(this.animator.bind(this))
}
};
this.animate = () => {
this.element = document.querySelector(`#${(this.id)} div`);
this.start = new Date().getTime();
this.animator();
}
}
function addLoader (){
let bar1 = new ProgressBar(40);
let container = document.querySelector("#container");
container.appendChild(bar1.create());
bar1.animate();
}
.p-bar{
width: 400px;
height: 20px;
background: 1px solid #ccc;
margin: 10px;
overflow:hidden;
border-radius: 4px;
}
.p-bar .loader{
width: 0;
background: #1565C0;
height: 100%;
}
<input type="button" value="Add loader" onclick="addLoader()" />
<div id="container"></div>
To simplify this question I have created two divs: when you click on the orange box the blue box below it will move back and forth in a continuous loop. What I want to be able to do is:
Click the orange box to start and STOP the blue box loop
After starting and stopping, the blue box will stop and continue each time where it left off.
I've tried just about everything and can't get it to work. Any advice would be greatly appreciated.
var hoverSlideBox = document.getElementById("hover_slide_box");
var slidingBox = document.getElementById("sliding_box");
hoverSlideBox.onclick = function() {
var pos = 0;
var moveLeft = false;
var stopAnimate = false;
init();
function init() {
setInterval(boxRight, 5);
}
function boxRight() {
if (!moveLeft) {
pos++;
slidingBox.style.left = pos + 'px';
}
if (pos == 500 || moveLeft) {
moveLeft = true;
boxLeft();
}
}
function boxLeft() {
if (moveLeft) {
pos--;
slidingBox.style.left = pos + 'px';
}
if (pos == 0 || !moveLeft) {
moveLeft = false;
}
}
}
<div id="hover_slide_box" style="background-color: #ff931e; cursor: pointer; position: absolute; height: 100px; width: 100px;"></div>
<div id="sliding_box" style="top: 120px; background-color: #0071bc; cursor: pointer; position: absolute; height: 100px; width: 100px;"></div>
You need to move some variables out of the onclick function so that they are not reset each time you click on the orange box. That, together with clearInterval, will give you a start/stop button.
var hoverSlideBox = document.getElementById("hover_slide_box");
var slidingBox = document.getElementById("sliding_box");
var running = false;
var intervalId;
var pos = 0;
var moveLeft = false;
hoverSlideBox.onclick = function() {
init();
function init() {
if (!running) {
intervalId = setInterval(boxRight, 5);
running = true;
} else {
clearInterval(intervalId);
running = false;
}
}
function boxRight() {
if (!moveLeft) {
pos++;
slidingBox.style.left = pos + 'px';
}
if (pos == 500 || moveLeft) {
moveLeft = true;
boxLeft();
}
}
function boxLeft() {
if (moveLeft) {
pos--;
slidingBox.style.left = pos + 'px';
}
if (pos == 0 || !moveLeft) {
moveLeft = false;
}
}
}
<div id="hover_slide_box" style="background-color: #ff931e; cursor: pointer; position: absolute; height: 100px; width: 100px;"></div>
<div id="sliding_box" style="top: 120px; background-color: #0071bc; cursor: pointer; position: absolute; height: 100px; width: 100px;"></div>
To do this, you can use clearInterval to stop the movement. To make it resume when you click again, you just need to have your position variable in a permanent scope (I move it to the global scope for simplicity).
Changed code:
var pos = 0;
var moveLeft = false;
var stopAnimate = false;
var slideInterval;
hoverSlideBox.onclick = function() {
init();
function init() {
if (slideInterval) {
clearInterval(slideInterval);
slideInterval = null;
} else {
slideInterval = setInterval(boxRight, 5);
}
}
Snippet:
var hoverSlideBox = document.getElementById("hover_slide_box");
var slidingBox = document.getElementById("sliding_box");
var pos = 0;
var moveLeft = false;
var stopAnimate = false;
var slideInterval;
hoverSlideBox.onclick = function() {
init();
function init() {
if (slideInterval) {
clearInterval(slideInterval);
slideInterval = null;
} else {
slideInterval = setInterval(boxRight, 5);
}
}
function boxRight() {
if (!moveLeft) {
pos++;
slidingBox.style.left = pos + 'px';
}
if (pos == 500 || moveLeft) {
moveLeft = true;
boxLeft();
}
}
function boxLeft() {
if (moveLeft) {
pos--;
slidingBox.style.left = pos + 'px';
}
if (pos == 0 || !moveLeft) {
moveLeft = false;
}
}
}
<div id="hover_slide_box" style="background-color: #ff931e; cursor: pointer; position: absolute; height: 100px; width: 100px;"></div>
<div id="sliding_box" style="top: 120px; background-color: #0071bc; cursor: pointer; position: absolute; height: 100px; width: 100px;"></div>
setInterval returns interval id which you can use to cancel the interval using clearInterval
You could cancel the timer as the other answer says, or use requestAnimationFrame which is specifically designed for animations like these. See documentation here
This way we can simply check if stopAnimating is set before queueing up our next frame. You could do the same thing with setInterval if you wanted to, but requestAnimationFrame is probably better.
var hoverSlideBox = document.getElementById("hover_slide_box");
var slidingBox = document.getElementById("sliding_box");
hoverSlideBox.onclick = function() {
var pos = 0;
var moveLeft = false;
var stopAnimate = false;
init();
function init() {
animate();
}
function animate(){
// stop animating without queueing up thenext frame
if (stopAnimate) return;
//queue up theh next frame.
requestAnimationFrame(boxRight);
}
function boxRight() {
if (!moveLeft) {
pos++;
slidingBox.style.left = pos + 'px';
}
if (pos == 500 || moveLeft) {
moveLeft = true;
boxLeft();
}
animate();
}
function boxLeft() {
if (moveLeft) {
pos--;
slidingBox.style.left = pos + 'px';
}
if (pos == 0 || !moveLeft) {
moveLeft = false;
}
}
}
<div id="hover_slide_box" style="background-color: #ff931e; cursor: pointer; position: absolute; height: 100px; width: 100px;"></div>
<div id="sliding_box" style="top: 120px; background-color: #0071bc; cursor: pointer; position: absolute; height: 100px; width: 100px;"></div>
Your code doesn't clear the interval when box is clicked again. setInterval executes a function every number of given seconds. To stop it, you have use the function clearInterval, passing the timer id that the function setInterval returned.
Also, your code can be better organized. I've refactorized it base in the following advices:
Don't use different functions for each direction. Your code have two different functions (moveLeft and moveRight) that are almost the same. Instead, use a simpler function that increments position given the current direction. Use a simple variable with the position increment, using -1 for left movement and +1 for right movement. If you have to modify the way the box moves you only have to change the moveBox function.
Use a function to check if the box have reached the boundaries. In general is good to have small functions that do one simple thing. In your code, boundaries check is splited in the two movement functions. In the proposed code the check code is a separate function where you can modify boundaries easily in the future if needed, instead of changing them in different places of your code.
Don't put all your logic in the click listener. Again, use simple functions that perform one simple task. The click listener should do a simple task, that's switching animation on and off. Initialization code should be in its onw function.
Again, for simplicity, use a simple function as the function executed by setInterval. If not, you would have to change the function that is executed by the setInterval function: moveRight when box is moving to the right and moveLeft when the box is moving to the left. This makes the code more complex and hard to debug and mantain.
Those advices are not very useful with a small code like this, but its benefits are more visible when your code gets more complex.
var hoverSlideBox = document.getElementById("hover_slide_box");
var slidingBox = document.getElementById("sliding_box");
var pos = 0;
var direction = 1;
var animate = false;
var intervalHandler;
hoverSlideBox.onclick = function() {
if (animate) {
stop();
}
else {
init();
}
}
function init() {
animate = true;
intervalHandler = setInterval(moveBox, 5);
}
function stop() {
animate = false;
clearInterval(intervalHandler);
}
function tic() {
if (animate) {
moveBox();
}
}
function checkBoundaries() {
if (pos == 500) {
direction = -1;
}
else if (pos == 0) {
direction = 1;
}
}
function moveBox() {
pos += direction;
slidingBox.style.left = pos + 'px';
checkBoundaries();
}
<div id="hover_slide_box" style="background-color: #ff931e; cursor: pointer; position: absolute; height: 100px; width: 100px;"></div>
<div id="sliding_box" style="top: 120px; background-color: #0071bc; cursor: pointer; position: absolute; height: 100px; width: 100px;"></div>
Try this snippet, it should work aswell
var hoverSlideBox = document.getElementById("hover_slide_box");
var slidingBox = document.getElementById("sliding_box");
var moveLeft = false;
var pos = 0;
var intervalID = null;
hoverSlideBox.onclick = function(){
if (intervalID) {
clearInterval(intervalID);
intervalID = null;
} else {
intervalID = setInterval(function () {
pos += moveLeft ? -1 : 1;
slidingBox.style.left = pos + 'px';
moveLeft = pos >= 500 ? true : pos <= 0 ? false : moveLeft;
}, 5);
}
}
<div id="hover_slide_box" style="background-color: #ff931e; cursor: pointer; position: absolute; height: 100px; width: 100px;">
<div id="sliding_box" style="top: 120px; background-color: #0071bc; cursor: pointer; position: absolute; height: 100px; width: 100px;">