So I working on my project and I want to make some section kind of Apple Scroll Sequence.
But when I convert the image sequence to .PNG, it's awful :(
So, here's the image lookalike when I scroll the section. It's like shadowing / emm I don't know what it is haha.
Here's my codes:
<div class="intermezo-sequence">
<div class="sticky-element">
<div class="sequence-element">
<canvas id="intermezo_canvas"></canvas>
</div>
</div>
</div>
const canvas = document.querySelector('#hero_canvas')
const ctx = canvas.getContext('2d')
const heroSequence = document.querySelector('.hero-sequence')
const images = []
var sequence1 = <?= json_encode($sequence1) ?>;
sequence1.sort();
const frameCount = sequence1.length; // biar dinamis total nya
const prepareImages = () => {
for (var i = 0; i < frameCount; i++) {
// console.log(sequence1[i]);
const image = new Image()
// image.src = `./assets/images/studycase/sequence/${i}.jpg`
canvas.width = 1928;
canvas.height = 1000;
image.src = sequence1[i];
images.push(image)
if (i === 0) {
images[i].onload = () => drawImage1(0)
}
}
}
const drawImage1 = frameIndex => {
ctx.drawImage(images[frameIndex], 0, 0)
}
prepareImages()
const heroSection = document.getElementById("heroSection");
window.addEventListener('scroll', () => {
const scrollTop = document.documentElement.scrollTop - heroSection.offsetTop
const maxScrollTop = heroSequence.scrollHeight - window.innerHeight
const scrollFraction = scrollTop / maxScrollTop
const frameIndex = Math.max(0, Math.min(frameCount - 1, Math.ceil(scrollFraction * frameCount)))
images[frameIndex].onload = () => drawImage1(frameIndex)
requestAnimationFrame(() => drawImage1(frameIndex))
})
Is there anything that I should add to the logic? Thanks!
Related
I am trying to build a tree in React wherein one can add nodes and connect them and move them around, like the included picture.
How does one go about doing this?
I am working with create-react-app
I have tried all sorts of things but I keep having trouble with event.clientX/event.pageX, it keeps giving me random values which results in the nodes flickering.
To me it makes most sense to have the node-moving functionality on the level of the tree but that approach gives this flickering problem.
How to prevent event.clientX from giving me random values?
The problem i normally have is the flickering of the sort in this codepen:
https://codesandbox.io/s/delicate-http-nnzx4?file=/src/App.js
(click and drag)
I have tried a number of things and the only way it works (altough buggy) is the way below where the node-moving functionality is on the level of the node:
import React, {useState,useEffect,useRef, useCallback, createRef} from 'react';
import "./PrinciplesTree.css"
function Line(props){
function clickhandler(e){
e.stopPropagation()
props.deletenodeconnection(props.firstpoint.node_number,props.secondpoint.node_number)
}
const firstpoint = props.firstpoint
const secondpoint = props.secondpoint
var x1 = firstpoint.anchor_pos.anchorposx
var y1 = firstpoint.anchor_pos.anchorposy
var x2 = secondpoint.anchor_pos.anchorposx
var y2 = secondpoint.anchor_pos.anchorposy
if (x2 < x1) {
var tmp;
tmp = x2 ; x2 = x1 ; x1 = tmp;
tmp = y2 ; y2 = y1 ; y1 = tmp;
}
var lineLength = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
var m = (y2 - y1) / (x2 - x1);
var degree = Math.atan(m) * 180 / Math.PI;
const divstyle = {transformOrigin: 'top left', transform: 'rotate(' + degree + 'deg)', width: lineLength + "px", height: 1 + 'px', background: 'black',
position: 'absolute', top: y1 + "px", left: x1 + "px"}
return <div className='line' style={divstyle} onClick={clickhandler}></div>
}
function Node(props) {
const [val, setval] = useState("Enter Principle");
const node_number = props.nodeN
const node_width = '150px'
const anchorel = useRef(null)
var offsetx = 0
var offsety = 0
let parentleft = 0
let parentright = 0
let parenttop = 0
let parentbottom = 0
const onclick = e =>{
e.stopPropagation();
const anchorpositionX = anchorel.current.getBoundingClientRect().left
const anchorpositionY = anchorel.current.getBoundingClientRect().top
const parentleft = e.target.parentElement.getBoundingClientRect().left
const parenttop = e.target.parentElement.getBoundingClientRect().top
const anchorpos = {anchorposx: anchorpositionX - parentleft, anchorposy: anchorpositionY - parenttop}
props.connectnode.current(node_number,anchorpos)
}
const movehandler = e => {
var newvalx = e.clientX-parentleft-offsetx
var newvaly = e.clientY-parenttop-offsety
if(((parentleft + newvalx) < parentright + 5 && (parentleft + newvalx) > parentleft - 5) &&
(parenttop + newvaly > parenttop - 5 && parenttop + newvaly < parentbottom + 5)){
props.updatenode(node_number,newvalx,newvaly)
}
}
const addmovehandler = e => {
const parent = e.target.parentElement
parentleft = parent.getBoundingClientRect().left
parentright = parent.getBoundingClientRect().right
parenttop = parent.getBoundingClientRect().top
parentbottom = parent.getBoundingClientRect().bottom
offsetx = e.clientX - e.target.getBoundingClientRect().left
offsety = e.clientY - e.target.getBoundingClientRect().top
document.addEventListener('mouseover',movehandler)
}
const removenodehandler = e =>{
const parent = e.target.parentElement
document.removeEventListener('mouseover',movehandler)
}
function edit(e){
e.stopPropagation();
if(e.key === 'Enter'){
setval(e.target.value)
}
}
return <div className='node' style = {{left: props.posX, top: props.posY, width: node_width}} onMouseDown={addmovehandler} onMouseUp = {removenodehandler} onClick = {onclick}>
<div className='anchor' ref={anchorel}></div>
<textarea className = 'principle' name = {val} onKeyDown={edit} placeholder = {val}></textarea>
<img src="cross.png" className='Cross' onClick={(e) => props.deletenode(node_number)}></img>
</div>
}
function PrinciplesTree() {
const [nodes, setnodes] = useState([]);
const [connectednodes, setconnectednodes] = useState([]);
const nodetoconnect = useRef(null)
const connectnoderef = useRef()
useEffect(() => {
setnodes([{key: 1, nodeN: 1, posX: 0, posY: 0, deletenode: deletenode, connectnode: connectnoderef, updatenode: updatenode}])
},[]);
const connectnode = (nodeN,anchorpos) => {
if(nodetoconnect.current == null){
nodetoconnect.current = {node_number: nodeN, anchor_pos: anchorpos}
}else if(nodetoconnect.current != null && nodeN != nodetoconnect.current.node_number )
{
const node_to_add = nodetoconnect.current
const firstnodenumber = nodetoconnect.current.node_number
const secondnodenumber = nodeN
var foundpair = false
connectednodes.forEach(connectednode => {
const firstnode = connectednode.first.node_number
const secondnode = connectednode.second.node_number
if((firstnode == firstnodenumber && secondnode == secondnodenumber) || (firstnode == secondnodenumber && secondnode == firstnodenumber)){
foundpair = true
}
})
const newnodetoconnect = {first: node_to_add, second: {node_number: nodeN, anchor_pos: anchorpos}}
if(foundpair == false){
setconnectednodes(connectednodes => [...connectednodes,newnodetoconnect])
}
nodetoconnect.current = null
}
}
connectnoderef.current = connectnode
function deletenodeconnection(node1,node2){
setconnectednodes(prevconnectednodes => {
return prevconnectednodes.filter(connectednodes => !(connectednodes.first.node_number == node1 && connectednodes.second.node_number == node2))
})
}
const deletenode = (NodeN) =>{
setnodes(prevnodes => {
return prevnodes.filter(node => node.nodeN !== NodeN)})
}
const updatenode = (NodeN,newposx,newposy)=> {
const updnode = {key: NodeN, nodeN: NodeN, posX: newposx, posY: newposy, deletenode: deletenode, connectnode: connectnoderef, updatenode: updatenode}
setnodes(nodes => (
nodes.map(node => {
if(node.nodeN == NodeN){
return updnode
}
else return node }
)))
}
function createnode(e){
var el = e.target
var posX=e.clientX-el.getBoundingClientRect().left
var posY=e.clientY-el.getBoundingClientRect().top
var newkey = 0;
nodes.forEach(node => {
if(node.key >= newkey){
newkey = parseInt(node.key) + 1
}
});
var newnode = {key: newkey, nodeN: nodes.length + 1, posX: posX, posY: posY, deletenode: deletenode, connectnode: connectnoderef, updatenode: updatenode}
setnodes(nodes => [...nodes, newnode]);
}
return <div onClick={createnode} className='TreeCanvas'>
{connectednodes.map(connectednode=> <Line firstpoint = {connectednode.first} secondpoint = {connectednode.second} deletenodeconnection={deletenodeconnection}/>)}
{nodes.map(node => <Node key = {node.key} nodeN = {node.nodeN} posX = {node.posX} posY = {node.posY} deletenode = {node.deletenode}
connectnode = {node.connectnode} updatenode = {node.updatenode}/>)}
</div>
}
export default PrinciplesTree;
Someone with the handle #wordswithjosh helped me a great deal on Reddit so I want to post his answer here for any people who might have the same question in the future.
Gotcha - I've had some difficulty in the past getting draggable components to update smoothly. Where I've found the most success is usually resisting the urge to use the onDrag or onMouseMove event in the component, instead just using the mouse movement event to remember the position of the cursor, and instead using requestAnimationFrame to actually visually move the component.
This seems like overkill at first, but when you want multiple components to visually update simultaneously, I've found that the most reliable pattern is something like this:
const TreeNode = () => {
const [originalLeft, setOriginalLeft] = useState(0); // very rarely is an initial value of 'undefined' desirable; this is one of those times
const [originalTop, setOriginalTop] = useState(0);
const [left, setLeft] = useState();
const [top, setTop] = useState();
const [originalMouseX, setOriginalMouseX] = useState(0);
const [originalMouseY, setOriginalMouseY] = useState(0);
const [newMouseX, setNewMouseX] = useState(0);
const [newMouseY, setNewMouseY] = useState(0);
const mutableFrameRef = useRef({ paused: true, lastFrame: null });
const selfRef = useRef(null);
const loop = () => {
// this shouldn't be necessary, but it's a failsafe to prevent
// runaway recursive function behavior I've experienced in the
// past when working with rAF
if (mutableFrameRef.current.paused) return;
// on every frame, set the new position of the div to its
// previous position plus the current offset of the mouse
setLeft(originalLeft + (newMouseX - originalMouseX));
setTop(originalY + (newMouseY - originalMouseY));
// this IS necessary, and is the default way of keeping a reference
// to the handle used to cancel the last requested frame
mutableFrameRef.current.lastFrame = requestAnimationFrame(loop);
};
// not destructuring these params for performance
const handleMouseMove = e => {
setNewMouseX(e.clientX);
setNewMouseY(e.clientY);
};
const handleMouseDown = ({ clientX, clientY }) => {
setOriginalMouseX(clientX);
setOriginalMouseY(clientY);
setNewMouseX(clientX);
setNewMouseY(clientY);
document.addEventListener('mousemove', handleMouseMove);
};
const handleMouseUp = ({ clientX, clientY }) => {
mutableFrameRef.paused = true;
cancelAnimationFrame(mutableFrameRef.current.lastFrame);
// one more time just to be sure
setLeft(originalLeft + (newMouseX - originalMouseX));
setTop(originalY + (newMouseY - originalMouseY));
document.removeEventListener('mousemove', handleMouseMove);
};
useEffect(() => {
// default both our original and "live" top left corner coordinates to what they are on first paint
const { x, y } = selfRef.current.getBoundingClientRect();
setOriginalLeft(x);
setOriginalTop(y);
setLeft(x);
setTop(y);
}, []);
return (
<div
ref={selfRef}
className='tree-node'
style={{
top,
left
}}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
draggable
>
// text stuff, tree node contents, etc - not relevant
</div>
);
};
Full disclosure, I haven't tested this exact config, I kinda wrote it live 😅 but I've used almost this exact pattern in the past, and I think you can get the idea - the main thing is that we only ask the browser to re-draw the div on every monitor refresh, which can drastically improve performance and help eliminate odd flickering.
You could, of course, avoid redrawing the div altogether by simply using the onDrag event to update the saved cursor position, and then update the actual drawn div location with onDragEnd, but I imagine the sort of "ghosting" behavior exhibited during the builtin HTML drag is not what you're looking for, and would not provide nearly as pretty of a user experience.
i try to encode the video block frame to specific pattern order, then in front-end it decode back to normal. I test it in back-end by decode back with same function, it back to normal. but in front-end with canvas the order of block not in right position. if you look into front-end function. it have same pattern. i try to check output from for generate, it equal to for in backend overlay builder command.
whats wrong with this?
ffmpeg config
const { spawn } = require('child_process');
function hflip(width) {
const sizeBlock = 16;
let filterCommands = '';
const length = Math.floor(width / sizeBlock);
for (let i=0; i < length; i++) {
filterCommands += `[0]crop=${sizeBlock}:480:${(i*sizeBlock)}:0[c${i}];`;
}
for (let i=0; i < length; i++) {
if (i == 0) filterCommands += '[0]';
if (i != 0) filterCommands += `[o${i}]`;
filterCommands += `[c${i}]overlay=x=${(width - sizeBlock)-(i*sizeBlock)}:y=0`;
if (i != (length - 1)) filterCommands += `[o${i+1}];`;
}
return filterCommands;
}
const crops = spawn('ffmpeg', [
'-i',
'C:/Software Development/project/blackpink.mp4',
'-filter_complex',
hflip(854),
'-c:a',
'copy',
'-c:v',
'libx264',
'-crf',
'30',
'-preset',
'ultrafast',
'-pix_fmt',
'yuv420p',
'C:/Software Development/project/hflip.mp4',
'-y'
], {
cwd: 'C:/Software Development/ffmpeg'
})
front-end
<!DOCTYPE html>
<html>
<head></head>
<body>
<button onclick="play()">play</button>
<script>
const canvas = document.createElement('canvas');
document.body.appendChild(canvas)
const context = canvas.getContext('2d');
const video = document.createElement('video');
video.src = 'https://drive.google.com/uc?export=download&id=1Z0aFg_N3kP0SUO_xOFB0UBjTRH6_mSmb&confirm=t'
function hflip(video) {
const widthBlock = 16;
const heightBlock = 480;
const length = Math.floor(video.videoWidth / widthBlock);
for (let i=0; i < length; i++) {
console.log({
cX: (i*widthBlock),
oX: (video.videoWidth - widthBlock) - (i*widthBlock)
});
context.drawImage(video, (i*widthBlock), 0, widthBlock, heightBlock, (video.videoWidth - widthBlock) - (i*widthBlock), 0, widthBlock, heightBlock)
}
}
video.onloadedmetadata = () => {
context.canvas.width = video.videoWidth;
context.canvas.height = video.videoHeight;
}
video.onplay = () => {
const updateCanvas = () => {
hflip(video);
video.requestVideoFrameCallback(updateCanvas);
}
updateCanvas();
}
function play() { video.play() }
</script>
</body>
</html>
I'm using pdf.js library to render my pdf file on canvas.
First I was searching a solution for rendering pdf with the size of canvas parent element.
I've found it and it works fine.
Then I solve the problem of rendering ALL pages at once.
Finally, my code now looks this way:
pdfjsLib.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjsLib.version}/pdf.worker.min.js`;
class PdfLoader {
currPage = 1;
numPages = -1;
doc = null;
constructor($container) {
this.$container = $container;
}
load(path) {
// reset when using more than once
this.currPage = 1;
this.promise = new Promise(resolve => this.promiseResolve = resolve);
this.$container.innerHTML = "";
pdfjsLib.getDocument(path).promise.then(pdf => {
this.doc = pdf;
this.numPages = pdf.numPages;
pdf.getPage(1).then(this._handlePage.bind(this));
});
return this;
}
_handlePage(page) {
let viewport = page.getViewport({scale: 1});
const scale = this.$container.clientWidth/viewport.width;
// const outputScale = window.devicePixelRatio || 1;
const outputScale = 1;
viewport = page.getViewport({scale});
const cnv = document.createElement("canvas");
const ctx = cnv.getContext("2d");
const width = Math.floor(viewport.width);
const height = Math.floor(viewport.height);
cnv.width = Math.floor(width * outputScale);
cnv.height = Math.floor(height * outputScale);
cnv.style.width = `${width}px`;
cnv.style.height = `${height}px`;
const transform = (outputScale !== 1) ? [outputScale, 0, 0, outputScale, 0, 0] : null;
page.render({
canvasContext: ctx,
transform: transform,
viewport: viewport,
});
this.$container.appendChild(cnv);
this.currPage++;
if (this.doc !== null && this.currPage <= this.numPages) {
this.doc.getPage(this.currPage).then(this._handlePage.bind(this));
} else {
this.promiseResolve();
}
}
}
const $pages = document.getElementById("pages");
const pdfLoader = new PdfLoader($pages);
pdfLoader.load("extendedReport.pdf").promise
.then(initReport);
let arrow = null;
function initReport() {
// my other stuff
}
And now my problem is that when viewing rendered pdf it looks like its quality is very low and text is blurred so the document is unreadable on mobile devices. I tried to change passed scale like they say on the internet, but that's not it. Could you help me, plz? What am I missing?
I would like to add multiple photos from the Array in this code to the elements, but it adds just one photo from the Array to the first Element.
I tried adding for loop, but I dont know where to start and where to end the loop. Could you please take a look to the code using the link (codepen)?
thank you
let zoomLevel = 1;
const images = [
{
thumb: 'http://localhost:8080/links/works/Print/001.webp',
hires: 'http://localhost:8080/links/works/Print/001.webp'
},
{
thumb: 'https://tasvir-graphic.de/links/works/digital/unterwelt.webp',
hires: 'https://tasvir-graphic.de/links/works/digital/unterwelt.webp'
}
]
// set to random image
let img = images[Math.floor(Math.random() * images.length)];
image.getElementsByTagName('a')[0].setAttribute('href', img.hires);
image.getElementsByTagName('img')[0].setAttribute('src', img.thumb);
const preloadImage = url => {
let img = new Image();
img.src = url;
}
preloadImage(img.hires);
const enterImage = function(e) {
zoom.classList.add('show', 'loading');
clearTimeout(clearSrc);
let posX, posY, touch = false;
if (e.touches) {
posX = e.touches[0].clientX;
posY = e.touches[0].clientY;
touch = true;
} else {
posX = e.clientX;
posY = e.clientY;
}
You can check this better using Codepen HERE.
const image = document.querySelectorAll('.image');
/* Store the number of all elements with css class 'image' */
let imageElementsCount = image.length;
for (index = 0; index < imageElementsCount; index++)
{
let arrayElementPos = Math.floor(Math.random() * images.length);
/* Receive the requested element from array with image objects */
let imageObject = images[arrayElementPos];
preloadImage(imageObject.hires);
/* Assign received image properties to your html element */
image[index].getElementsByTagName('a')[0].setAttribute('href', imageObject.hires);
image[index].getElementsByTagName('img')[0].setAttribute('src', imageObject.thumb);
image[index].addEventListener('mouseover', enterImage);
image[index].addEventListener('touchstart', enterImage);
image[index].addEventListener('mouseout', leaveImage);
image[index].addEventListener('touchend', leaveImage);
image[index].addEventListener('mousemove', move);
image[index].addEventListener('touchmove', move);
image[index].addEventListener('wheel', e =>
{
e.preventDefault();
e.deltaY > 0 ? zoomLevel-- : zoomLevel++;
if (zoomLevel < 1) zoomLevel = 1;
if (zoomLevel > 5) zoomLevel = 5;
console.log(`zoom level: ${zoomLevel}`);
zoom.style.transform = `scale(${zoomLevel})`;
});
}
The loop is working until all founded divs got an assignment.
ToDos:
Remove in line
const image = document.querySelectorAll('.image')[0];
the [0].
Next step: Take a look into the body of for loop. Remove your lines of code in your original code
Thank you #Reporter, but I've allready done this editing my code for two days. :)
const zoo = document.querySelectorAll('.zoom');
const zooImg = document.querySelectorAll('.zoom-image');
const pic = document.querySelectorAll(".image");
let clearSrc;
let zoomLevel = 1;
const digiImgs = [{
thumb: 'https://tasvir-graphic.de/links/works/digital/MuZe.webp',
hires: 'https://tasvir-graphic.de/links/works/digital/MuZe.webp'
},
{
thumb: 'https://tasvir-graphic.de/links/works/digital/unterwelt.webp',
hires: 'https://tasvir-graphic.de/links/works/digital/unterwelt.webp'
},
{
thumb: 'https://tasvir-graphic.de/links/works/digital/takeCare.webp',
hires: 'https://tasvir-graphic.de/links/works/digital/takeCare.webp'
},
]
// set to random image
for (var i = 0; i < pic.length; i++) {
let img = digiImgs[i];
pic[i].getElementsByTagName('a')[0].setAttribute('href', img.hires);
pic[i].getElementsByTagName('img')[0].setAttribute('src', img.thumb);
const preloadImage = url => {
let img = new Image();
img.src = url;
}
preloadImage(img.hires);
const enterImage = function (e) {
var zoo = this.parentNode.childNodes[3];
zoo.classList.add('show', 'loading');
clearTimeout(clearSrc);
let posX, posY, touch = false;
if (e.touches) {
posX = e.touches[0].clientX;
posY = e.touches[0].clientY;
touch = true;
} else {
posX = e.clientX;
posY = e.clientY;
}
touch
?
zoo.style.top = `${posY - zoo.offsetHeight / 1.25}px` :
zoo.style.top = `${posY - zoo.offsetHeight / 2}px`;
zoo.style.left = `${posX - zoo.offsetWidth / 2}px`;
let originalImage = this.getElementsByTagName('a')[0].getAttribute('href');
var zoImg = this.parentNode.childNodes[3].childNodes[1];
zoImg.setAttribute('src', originalImage);
// remove the loading class
zoImg.onload = function () {
setTimeout(() => {
zoo.classList.remove('loading');
}, 500);
}
}
const leaveImage = function () {
// remove scaling to prevent non-transition
var zoImg = this.parentNode.childNodes[3].childNodes[1];
var zoo = this.parentNode.childNodes[3];
zoo.style.transform = null;
zoomLevel = 1;
zoo.classList.remove('show');
clearSrc = setTimeout(() => {
zoImg.setAttribute('src', '');
}, 250);
}
const move = function (e) {
e.preventDefault();
var zoImg = this.parentNode.childNodes[3].childNodes[1];
var zoo = this.parentNode.childNodes[3];
let posX, posY, touch = false;
if (e.touches) {
posX = e.touches[0].clientX;
posY = e.touches[0].clientY;
touch = true;
} else {
posX = e.clientX;
posY = e.clientY;
}
// move the zoom a little bit up on mobile (because of your fat fingers :<)
touch ?
zoo.style.top = `${posY - zoo.offsetHeight / 1.25}px` :
zoo.style.top = `${posY - zoo.offsetHeight / 2}px`;
zoo.style.left = `${posX - zoo.offsetWidth / 2}px`;
let percX = (posX - this.offsetLeft) / this.offsetWidth,
percY = (posY - this.offsetTop) / this.offsetHeight;
let zoomLeft = -percX * zoImg.offsetWidth + (zoo.offsetWidth / 2),
zoomTop = -percY * zoImg.offsetHeight + (zoo.offsetHeight / 2);
zoImg.style.left = `${zoomLeft}px`;
zoImg.style.top = `${zoomTop}px`;
}
pic[i].addEventListener('mouseover', enterImage);
pic[i].addEventListener('touchstart', enterImage);
pic[i].addEventListener('mouseout', leaveImage);
pic[i].addEventListener('touchend', leaveImage);
pic[i].addEventListener('mousemove', move);
pic[i].addEventListener('touchmove', move);
pic[i].addEventListener('wheel', e => {
var zoo = e.target.parentNode.parentNode.parentNode.childNodes[3];
console.log(zoo);
e.preventDefault();
e.deltaY > 0 ? zoomLevel-- : zoomLevel++;
if (zoomLevel < 1) zoomLevel = 1;
if (zoomLevel > 3) zoomLevel = 3;
console.log(`zoom level: ${zoomLevel}`);
zoo.style.transform = `scale(${zoomLevel})`;
});
}
but there is a problem with the touch. When I touch using mobile, the photo works like a link and opens the photo. How to use preventDefault Touch?
Im trying to build an audio progess bar with react hooks. I was following a tutorial with react class based components but got a little lost with refs.
How can I give my useRef variables an initial value of the div ref when the page loads?
As soon as I start playing then the I get an error saying cant read offsetwidth of null. Obviously timeline ref is null as it doesnt have an initial value. How can I connect it to the div with id of timeline in the useEffect hook?
const AudioPlayer = () => {
const url = "audio file";
const [audio] = useState(new Audio(url));
const [duration, setDuration] = useState(0);
const [currentTime, setCurrentTime] = useState(0)
let timelineRef = useRef()
let handleRef = useRef()
useEffect(() => {
audio.addEventListener('timeupdate', e => {
setDuration(e.target.duration);
setCurrentTime(e.target.currentTime)
let ratio = audio.currentTime / audio.duration;
let position = timelineRef.offsetWidth * ratio;
positionHandle(position);
})
}, [audio, setCurrentTime, setDuration]);
const mouseMove = (e) => {
positionHandle(e.pageX);
audio.currentTime = (e.pageX / timelineRef.offsetWidth) * audio.duration;
};
const mouseDown = (e) => {
window.addEventListener('mousemove', mouseMove);
window.addEventListener('mouseup', mouseUp);
};
const mouseUp = (e) => {
window.removeEventListener('mousemove', mouseMove);
window.removeEventListener('mouseup', mouseUp);
};
const positionHandle = (position) => {
let timelineWidth = timelineRef.offsetWidth - handleRef.offsetWidth;
let handleLeft = position - timelineRef.offsetLeft;
if (handleLeft >= 0 && handleLeft <= timelineWidth) {
handleRef.style.marginLeft = handleLeft + "px";
}
if (handleLeft < 0) {
handleRef.style.marginLeft = "0px";
}
if (handleLeft > timelineWidth) {
handleRef.style.marginLeft = timelineWidth + "px";
}
};
return (
<div>
<div id="timeline" ref={(timeline) => { timelineRef = timeline }}>
<div id="handle" onMouseDown={mouseDown} ref={(handle) => { handleRef = handle }} />
</div>
</div>
)
}
The useRef() hook returns a reference to an object, with the current property. The current property is the actual value useRef points to.
To use the reference, just set it on the element:
<div id="timeline" ref={timelineRef}>
<div id="handle" onMouseDown={mouseDown} ref={handleRef} />
And then to use it, you need to refer to the current property:
let position = current.timelineRef.offsetWidth * ratio;
And positionHandle - you shouldn't actually set styles on elements in React in this way. Use the setState() hook, and set the style using JSX.
const positionHandle = (position) => {
let timelineWidth = timelineRef.current.offsetWidth - handleRef.current.offsetWidth;
let handleLeft = position - timelineRef.current.offsetLeft;
if (handleLeft >= 0 && handleLeft <= timelineWidth) {
handleRef.current.style.marginLeft = handleLeft + "px";
}
if (handleLeft < 0) {
handleRef.current.style.marginLeft = "0px";
}
if (handleLeft > timelineWidth) {
handleRef.current.style.marginLeft = timelineWidth + "px";
}
};
In addition, the ref can be also used for other values, such as the new Audio(url), and be extracted from the current property:
const { current: audio } = useRef(new Audio(url));