I have a div and a textarea inside a parent div. I am trying to copy the scrollTop value of the textarea to the div so it moves in sync with the textarea scrolling.
The problem seems to be when i add text into the textarea and then press enter for a new line, the div scrollTop value doesn't seem to update but the textarea scrollTop value does.
If i press enter again both values update but it seems the div scrollTop value is one step behind the textarea
https://codesandbox.io/s/objective-feather-ngq8t
handleScroll = (e) => {
setTextareaScrollTop(e.target.scrollTop);
e.target.previousElementSibling.scrollTop = e.target.scrollTop;
setDivScrollTop(e.target.previousElementSibling.scrollTop);
};
One simple workaround is to remove the setDivScrollTop from the handleScroll and add a new line \n after setting the red div's text. Note that this character acts like a caret and allows it to follow the other div.
handleScroll = (e) => {
setTextareaScrollTop(e.target.scrollTop);
e.target.previousElementSibling.scrollTop = e.target.scrollTop;
// setDivScrollTop(e.target.scrollTop);
};
handleInput = (e) => {
console.log(divScrollTop, textareaScrollTop)
setText(e.target.value + "\n"); // add "\n"
};
As seen here, Codesandbox
Also I've added border style to the text area element and spellCheck={false} to make it possible to see they're equal.
I made some mod to your code, https://codesandbox.io/s/empty-voice-7w3ze
const useUpdate = () => {
const [, dispatch] = useState(0);
const ref = useRef(() => {
dispatch((v) => v + 1);
});
return ref.current;
};
And when you need to repaint, just do
handleScroll = (e) => {
e.target.previousElementSibling.scrollTop = e.target.scrollTop;
refresh();
};
I didn't answer your question exactly according to what you want, but i noticed, there's no role the setState plays, so i removed both of them and replaced with a useUpdate. Let me know what you think on this approach.
If i remove both setState you had earlier, i do see the issue you described.
Related
I have this code that should change the contents of a table position when you change the contents of nameDiv which is var nameDiv = document.createElement('div'); (nameDiv.contentEditable = "true";) and it has some text
nameDiv.onclick = event => {
allSectionsArray[sectionNumber][2] = event.value;
}
What happens here is basically: it updates the second I click on the div and not after some text is entered.
What I want is: to save changes after entering the text. Is there any substitute of onclick or any other method that I can achieve this?
For contenteditable, you can use the input event:
The input event fires when the value of an input element has been changed.
The change event isn't supported for contenteditable, but you can use the blur event instead which
fires when an element has lost focus
const example = document.querySelector("#example");
example.addEventListener("blur", ()=>{
console.log(`Value is: ${example.textContent}`);
});
#example {
border: solid 1px #000;
padding: 5px;
}
<div id="example" contenteditable></div>
You can also browse the full list of events here.
Find below a sample how different events work with contenteditable elements.
You will probably need blur event handler to get the value when user is done typing.
const code = document.querySelector('pre');
const object = {
valueOnclick: '',
valueOninput: '',
valueOnblur: ''
};
document.querySelector('div').addEventListener('input', e => {
object.valueOninput = e.target.textContent;
code.innerHTML = JSON.stringify(object, null, 4);
});
document.querySelector('div').addEventListener('click', e => {
object.valueOnclick = e.target.textContent;
code.innerHTML = JSON.stringify(object, null, 4);
});
document.querySelector('div').addEventListener('blur', e => {
object.valueOnblur = e.target.textContent;
code.innerHTML = JSON.stringify(object, null, 4);
});
<div contenteditable>Some content</div>
<pre></pre>
So my issue here is quite simple but you don't have to understand the others codes just only the useEffect() parts..
My custom mousecursor text is doubling when I tried to hover the text
here is the lines of codes.
const cursorIntro = document.querySelector(".cursor");
const options = document.querySelector(".introduction .nav-options");
options.addEventListener("mousemove", function s(e) {
var rect = options.getBoundingClientRect();
var x = e.clientX - rect.left; //x position within the element.
var y = e.clientY - rect.top;
cursorIntro.style.left = x + "px";
cursorIntro.style.top = y + "px";
});
function OnSelect() {
const optionsSelection = document.querySelectorAll(".options");
optionsSelection.forEach((elem, i) => {
// console.log(elem.children[1].children[0].children[0])
elem.children[1].children[0].children[0].addEventListener(
"mouseleave",
() => {
cursorIntro.removeChild(cursorIntro.lastChild);
// cursorIntro.innerHTML = ""
}
);
elem.children[1].children[0].children[0].addEventListener(
"mouseenter",
() => {
// elem.children[1].children[0].children[0].classList.add('')
const createElement = document.createElement("h4");
createElement.innerText =
elem.children[1].children[0].children[0].dataset.name;
cursorIntro.appendChild(createElement);
}
);
});
}
OnSelect();
As you see I have a custom mousecursor on it and because that is where I want to append the text when it hover the text elements.
This is inside the useEffect() when I'm calling it...but one that I wasn't sure is that I only call back once the addEventListener per each.
The reason I used createElement because if I used innerHTML without using a createElement I can't add another some items because my plan here is to added something more in mousecursor
THIS IS THE CODEPEN
go to index.js and replace StrictMode to React.Fragment, in dev mode react re-renders twice
Above you can see code , which I try to making work. The idea here is , I have contentEditable div element , in which I type some text , I have button bold , which you can click , it will create inside of my div, new strong element , and will put caret to this element , for giving me opportunity to type in this element . Seems like it put caret to this element , but when I start typing , it types in previos element. How can I put cater in new created element. I also craete it on this element , maybe there is a way to put cater on element by id ? Or any other abailable solution for my case .
import { useEffect, useState, useRef } from "react";
import "./styles.css";
const id = "textarea";
const Buttons = {
bold: "bold",
custom: "custom"
};
export default function App() {
const [button, setButton] = useState();
const buttons = [
{
text: "Bold",
type: Buttons.bold
}
];
const onButtonClick = (buttonValue) => {
if (button === buttonValue) {
setButton(Buttons.custom);
} else {
setButton(buttonValue);
}
};
const prevButton = usePrevious(button);
useEffect(() => {
if (prevButton !== button) {
const element = document.getElementById(id);
if (element) {
console.log(element);
if (button === Buttons.bold) {
const strong = document.createElement("strong");
const id = generateUuid();
strong.id = id;
element.appendChild(strong);
const createdEl = document.getElementById(id);
const range = document.createRange();
const sel = document.getSelection();
if (createdEl) {
console.log(createdEl, "createdEl");
range.setStart(createdEl, 0);
range.setEnd(createdEl, 0);
}
if (sel) {
console.log(sel, "sel");
sel.removeAllRanges();
sel.addRange(range);
}
}
}
}
}, [prevButton, button]);
return (
<div>
<div contentEditable id={id} className="rich-text" />
{buttons.map((buttonEntity) => {
return (
<button onClick={() => onButtonClick(buttonEntity.type)}>
{buttonEntity.text}
</button>
);
})}
</div>
);
}
const usePrevious = (value) => {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
};
export const generateUuid = () =>
Date.now().toString(36) + Math.random().toString(36).substring(2);
All logic described in useEffect by creating new element , and put cater to this element. Important to mention, if you will set some innerHtml text to new created element , and will set
range.setStart(createdEl, 1);
range.setEnd(createdEl, 1);
then it will be working , but if no innerHtml inside , it doesn't work
contenteditable behavior is weird, element.focus() doesn't work directly, but it does with a setTimeout(), in some of the cases:
const div = document.getElementById('textarea');
setTimeout(() => div.focus(), 0);
You might want to add tabindex=0 to allow users to keyboard navigate into it.
Regardless, using content editable as a full editor will probably give you a bad time, it's a little messy and hard to control. You might want to do this with a hidden input.
Also, this document suggests you can make a rich text editor using content editable, but it seems deprecated - https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Editable_content
I've got a fixed height div with a list of clickable list items. In the middle of the div, I have an absolute positioned line that is meant to signify a selected item. Right now, it's just a static line.
Is there a way to add an active class to the list item as it is "selected" by the line?
http://dev.chrislamdesign.com/shortwave/
One of the solutions is to use document.elementFromPoint(x, y) method. Something like this:
let lineCoords, lineTop, lineCenter;
// try to remove these two lines, leave just the scroll event listener
// document.getElementById('scrollUp1').addEventListener('click', setActive);
// document.getElementById('scrollDown1').addEventListener('click', setActive);
// 2nd edition: added these event listeners
window.addEventListener('scroll', getLineCoords);
window.addEventListener('load', getLineCoords);
// added this line
document.getElementById('wrap-scroll-1').addEventListener('scroll', setActive);
function setActive() {
const li = document.elementFromPoint(lineCenter, lineTop + lineCoords.height);
clearActive();
li.classList.add('active');
}
function clearActive() {
const ul = document.getElementById('ul-scroll-1');
const activeLi = ul.querySelector('li.active');
if (activeLi) {
activeLi.classList.remove('active');
}
}
// 2nd edition: added this function
function getLineCoords() {
lineCoords = document.querySelector('.orange-line').getBoundingClientRect();
lineTop = lineCoords.top;
lineCenter = lineCoords.left + (lineCoords.width / 2);
}
You can see this in action here: JsBin. These up and down buttons are assumed to scroll the list, but I don't have this functionality, because that's not a point here - just scroll it youself. The point here is that the element under the orange line will get active class each time you click one of these buttons.
So, take this code and edit it as you want.
Edited: I added an scroll event listener to the #wrap-scroll-1 container, because I guess the scroll event occurs right on it. If not - you can change it. Look at this in action: JsBin
2nd edition: Added event listeners to reassign the orange line coordinates every time when the page scrolled, and also when the page is loaded. Take a look at the result here: JsBin
You could compare the rects of the line and each option to find which is selected:
const line = document.querySelector('#emotional .orange-line');
const options = document.querySelector('#emotional .selection-options').children;
const lineY = line.getBoundingClientRect().y;
const optionAfterSelected = [...options].find((option) => {
return option.getBoundingClientRect().y > lineY;
});
const selected = optionAfterSelected.previousSibling;
selected.classList.add('selected');
The selected option is the one with the largest y value without exceeding the y value of the orange line. To make things simple, the loop just returns the first option with a y value greater than the line, then grabs its previous sibling.
Update
To get this code to run whenever the user scrolls, you can wrap it in a function and attach it as an eventListener:
function updateSelection(menuId) {
const line = document.querySelector(menuId + ' .orange-line');
const options = document.querySelector(menuId + ' .selection-options').children;
const lineY = line.getBoundingClientRect().y;
const optionAfterSelected = [...options].find((option) => {
return option.getBoundingClientRect().y > lineY;
});
const selected = optionAfterSelected.previousSibling;
selected.classList.add('selected');
}
document.querySelector('#emotional .wrap-container').addEventListener('wheel', () => {
updateSelection('#emotional');
});
document.querySelector('#genre .wrap-container').addEventListener('wheel', () => {
updateSelection('#genre');
});
document.querySelector('#cinematic .wrap-container').addEventListener('wheel', () => {
updateSelection('#cinematic');
});
I have made this
https://jsfiddle.net/a4376mr8/
When I drag and drop the image div to a new div, why is it not there in the previous div?
const boxes = document.querySelectorAll('.box');
const imageBox = document.querySelector('#draggableItem');
for (const box of boxes) {
box.addEventListener('dragover', function (e) {
e.preventDefault();
this.className += ' onhover';
})
box.addEventListener('dragleave', function () {
this.className = 'box';
})
box.addEventListener('drop', function (e) {
this.className = 'box';
this.append(imageBox);
})
}
If I understood your needs, that the image be repeated on drag and drop, you need to clone your div tag dom object. Since js just sees the reference to it, when you simply append it, this causes it to just move from place to place instead of duplicating.
So instead of just appending, clone the node as follows (line 15 of your fiddle's js).
this.append(imageBox.cloneNode(true));
See here: https://developer.mozilla.org/en-US/docs/Web/API/Node/cloneNode
Updated fiddle: https://jsfiddle.net/a4376mr8/1/