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>
Related
I have a container that is expanded and collapsed on click of chevron icon. The code to collapse/expand the container is in the function transformAnimation. The code of transformAnimation is similar to the code on MDN web docs for requestAnimationFrame. The code to animate (scale) the container has been developed on the guidelines of this article on Building performant expand & collapse animations on Chrome Developers website.
I am not able to figure out how to calculate yScale value (which is nothing but y scale values for collapse/expand animation) as a function of the time elapsed since the start of the animation.
To elaborate what I mean, let's assume that the container is in expanded state. In this state the scaleY value of the container is 6. Now when user clicks on the toggle button, in the transformAnimation function for each animation frame, i.e, execution of the requestAnimationFrame callback step function, the value of scaleY should decrease from 6 (the expanded state) to 1 (the collapsed state) in the exact duration that I want the animation to run for.
In the present state, the code to calculate yScale is not working as expected.
const dragExpandableContainer = document.querySelector('.drag-expandable-container');
const dragExpandableContents = document.querySelector('.drag-expandable__contents');
const resizeableControlEl = document.querySelector('.drag-expandable__resize-control');
const content = document.querySelector(`.content`);
const toggleEl = document.querySelector(`.toggle`);
const collapsedHeight = calculateCollapsedHeight();
/* This height is used as the basis for calculating all the scales for the component.
* It acts as proxy for collapsed state.
*/
dragExpandableContainer.style.height = `${collapsedHeight}px`;
// Apply iniial transform to expand
dragExpandableContainer.style.transformOrigin = 'bottom left';
dragExpandableContainer.style.transform = `scale(1, 10)`;
// Apply iniial reverse transform on the contents
dragExpandableContents.style.transformOrigin = 'bottom left';
dragExpandableContents.style.transform = `scale(1, calc(1/10))`;
let isOpen = true;
const togglePopup = () => {
if (isOpen) {
collapsedAnimation();
toggleEl.classList.remove('toggle-open');
isOpen = false;
} else {
expandAnimation();
toggleEl.classList.add('toggle-open');
isOpen = true
};
};
function calculateCollapsedHeight() {
const collapsedHeight = content.offsetHeight + resizeableControlEl.offsetHeight;
return collapsedHeight;
}
const calculateCollapsedScale = function() {
const collapsedHeight = calculateCollapsedHeight();
const expandedHeight = dragExpandableContainer.getBoundingClientRect().height;
return {
/* Since we are not dealing with scaling on X axis, we keep it 1.
* It can be inverse to if required */
x: 1,
y: expandedHeight / collapsedHeight,
};
};
const calculateExpandScale = function() {
const collapsedHeight = calculateCollapsedHeight();
const expandedHeight = 100;
return {
x: 1,
y: expandedHeight / collapsedHeight,
};
};
function expandAnimation() {
const {
x,
y
} = calculateExpandScale();
transformAnimation('expand', {
x,
y
});
}
function collapsedAnimation() {
const {
x,
y
} = calculateCollapsedScale();
transformAnimation('collapse', {
x,
y
});
}
function transformAnimation(animationType, scale) {
let start, previousTimeStamp;
let done = false;
function step(timestamp) {
if (start === undefined) {
start = timestamp;
}
const elapsed = timestamp - start;
if (previousTimeStamp !== timestamp) {
const count = Math.min(0.1 * elapsed, 200);
//console.log('count', count);
let yScale;
if (animationType === 'expand') {
yScale = (scale.y / 100) * count;
} else yScale = scale.y - (scale.y / 100) * count;
//console.log('yScale', yScale);
if (yScale < 1) yScale = 1;
dragExpandableContainer.style.transformOrigin = 'bottom left';
dragExpandableContainer.style.transform = `scale(${scale.x}, ${yScale})`;
const inverseXScale = 1;
const inverseYScale = 1 / yScale;
dragExpandableContents.style.transformOrigin = 'bottom left';
dragExpandableContents.style.transform = `scale(${inverseXScale}, ${inverseYScale})`;
if (count === 200) done = true;
//console.log('elapsed', elapsed);
if (elapsed < 1000) {
// Stop the animation after 2 seconds
previousTimeStamp = timestamp;
if (!done) requestAnimationFrame(step);
}
}
}
requestAnimationFrame(step);
}
.drag-expandable-container {
position: absolute;
bottom: 0px;
display: block;
overflow: hidden;
width: 100%;
background-color: #f3f7f7;
}
.drag-expandable__contents {
height: 0;
}
.toggle {
position: absolute;
top: 2px;
right: 15px;
height: 10px;
width: 10px;
transition: transform 0.2s linear;
}
.toggle-open {
transform: rotate(180deg);
}
.drag-expandable__resize-control {
background-color: #e7eeef;
}
.burger-icon {
width: 12px;
margin: 0 auto;
padding: 2px 0;
}
.burger-icon__line {
height: 1px;
background-color: #738F93;
margin: 2px 0;
}
.drag-expandable__resize-control:hover {
border-top: 1px solid #4caf50;
cursor: ns-resize;
}
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="css.css">
</head>
<body>
<div class="drag-expandable-container">
<div class="drag-expandable__contents">
<div class="drag-expandable__resize-control">
<div class="burger-icon">
<div class="burger-icon__line"></div>
<div class="burger-icon__line"></div>
<div class="burger-icon__line"></div>
</div>
</div>
<div class="content" />
<div>
<div class="toggle toggle-open" onclick="togglePopup()">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.1.1 by #fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M416 352c-8.188 0-16.38-3.125-22.62-9.375L224 173.3l-169.4 169.4c-12.5 12.5-32.75 12.5-45.25 0s-12.5-32.75 0-45.25l192-192c12.5-12.5 32.75-12.5 45.25 0l192 192c12.5 12.5 12.5 32.75 0 45.25C432.4 348.9 424.2 352 416 352z"/></svg>
</div>
</div>
</div>
</div>
</body>
<script type="text/javascript" src="js.js"></script>
</html>
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
Today i'm asking myself how to create a duration parameter like GreenSock does with its functions.
To be clear, how to create an animating function that takes a duration in pure vanilla JavaScript ?
Are they doing it by adding a transition-duration with JavaScript ? :)
I would like a solution in plain JS.
I have some ideas, maybe width :
new Date()
Or some :
setInterval()
For example how to handle the duration here ?
HTML :
<div class="box"></div>
CSS
.box {
width: 200px;
height: 200px;
background: orange;
}
JavaScript
const box = document.querySelector('.box');
function move(duration){
box.style.transform = "translateX(50px)";
}
move(2000)
Thank's if someone is passing by to give me a hand ! :)
const box = document.querySelector('.box')
function move(duration){
box.style['transition-property'] = 'transform'
box.style['transition-duration'] = `${duration}ms`
setTimeout(() => box.style.transform = "translate3d(200px,0,0)")
}
move(2000)
.box {
width: 200px;
height: 200px;
background: orange;
}
<div class="box"></div>
If you don't want to leverage CSS for the transition control then you will have to control it manually using requestAnimationFrame and your own easing function:
const EASE_IN_OUT = (t) =>
t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1
const ease = (f, easing = EASE_IN_OUT) => (...args) => easing(f(...args))
const limit = (f, limit = 1) => (...args) =>
{ const result = f(...args); return result > limit ? limit : result }
const elapsedFraction = ({ start, duration }) =>
(performance.now() - start) / duration
const asInteger = (f) => (...args) => f(...args).toFixed(0)
const calcX = asInteger(({ start, duration, distance }) =>
ease(limit(elapsedFraction))({ start, duration }) * distance)
function move({ el, duration, distance }) {
const start = performance.now()
const startX = el.getBoundingClientRect().x
const tick = () => {
if (el.getBoundingClientRect().x - startX === distance) return
el.style.transform = `translate3d(${calcX({ start, duration, distance })}px,0,0)`
requestAnimationFrame(tick)
}
tick()
}
const el = document.querySelector(".box")
move({ el, duration: 1000, distance: 200 })
.box {
width: 200px;
height: 200px;
background: orange;
}
<div class="box"></div>
I think the best way is to use an animation library. But, I made an example by using "translate3d". Translate3d runs on the GPU and not in the CPU, because of this is more performant. I put a pure CSS example as well.
const box = document.querySelector('.box');
const blue = document.querySelector('.blue');
function move(elem, duration){
let posx = elem.getBoundingClientRect().x
let counter = duration
const timer = setInterval(() => {
if (counter <= 0) {
clearInterval(timer)
}
posx += 2
elem.style.transform = `translate3d(${posx}px,0,0)`
counter -= 10
}, 10)
}
move(box, 2000)
setTimeout(() => blue.classList.add('act'), 1000)
.box {
width: 60px;
height: 60px;
background: orange;
}
.blue {
width: 60px;
height: 60px;
transform: translate3d(0,0,0);
background: lightblue;
transition: all 0.8s ease;
}
.act {
transform: translate3d(100px,0,0)
}
<div class="box"></div>
<br>
<div class="blue"></div>
I think this function do what you want:
Element.prototype.move = function(duration, distance) {
this.style.transition = duration + 's';
this.style.transform = 'translateX(' + distance + 'px)';;
};
.box {
width: 200px;
height: 200px;
background: orange;
}
<div class="box" onclick="this.move(2, 400);">Click me!</div>
why my checkCollision function is not working in foreach loop ? I want to check whether obs (obstacle) is hits/overlaps on collector (gray object). I am checking every 1 millisecond using setInterval checkCollision function. Basically I am trying to build a simple car game. please help me and thank you in advance
let body = document.body[0];
let container = document.querySelector(".container");
let allObstacles = [];
let colors = ["green", "green", "red"];
let collector = document.getElementById("collector");
class Obstacle {
constructor(yPos) {
this.yPos = -50;
}
randomnum() {
let randX = Math.floor(Math.random() * (container.clientWidth - 50));
return randX;
}
createObstacle() {
let obstacle = document.createElement("div");
obstacle.classList.add("obstacle");
let bgColor = colors[Math.floor(Math.random() * colors.length)];
obstacle.style.width = "50px";
obstacle.style.height = "50px";
obstacle.style.position = "absolute";
obstacle.style.left = this.randomnum() + "px";
obstacle.style.top = this.yPos + "px";
obstacle.style.backgroundColor = bgColor;
obstacle.dataset.behave = bgColor;
container.appendChild(obstacle);
return obstacle;
}
element = this.createObstacle();
updatePosition() {
this.yPos += 2;
this.element.style.top = this.yPos + "px";
}
kill() {
this.element.remove();
}
}
let dropObs = setInterval(function() {
allObstacles.forEach(function(obs) {
obs.updatePosition();
});
allObstacles.forEach(function(obs) {
// why checkCollision function is not working?
if (checkCollision(obs, collector)) {
console.log("hit");
}
if (obs.yPos > container.clientHeight) {
obs.kill();
}
});
}, 10);
let generateObs = setInterval(function() {
let obs = new Obstacle();
allObstacles.push(obs);
}, 2000);
function checkCollision(obj1, obj2) {
var obj1Y = obj1.offsetTop;
var obj2Y = obj2.offsetTop;
var obj1X = obj1.offsetLeft;
var obj2X = obj2.offsetLeft;
if (
obj2Y + 100 >= obj1Y &&
obj2Y <= obj1Y + 100 &&
obj2X + 100 >= obj1X &&
obj2X <= obj1X + 100
) {
return 1;
}
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
outline: 0;
}
html,
body {
width: 100%;
height: 100%;
}
.container {
position: relative;
width: 100%;
height: 100%;
}
#collector {
width: 50px;
height: 100px;
background: gray;
position: absolute;
top: calc(100vh - 100px);
left: 50%;
margin-left: -25px;
}
<div class="container">
<div id="collector"></div>
</div>
The first thing you should not use setInterval for this type of animation. Use requestAnimationFrame
https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
obj1X,obj1Y comming undefined.