removeChild sometimes removes entire span and sometimes doesn't - javascript

I'm working on a rich text editor for iOS and have most of it working but running into endless problems ensuring that the cursor is visible in the viewport when the user starts typing.
I came up with a novel approach: insert a span at the cursor position, scroll to the span, and then remove it. (I haven't gotten to only scrolling if the span is on-screen.) Here's what I wrote:
document.addEventListener('keypress', function(e) {
jumpToID();
}, false);
function jumpToID() {
var id = "jumphere2374657";
var text = "<span id='" + id + "'> </span>"
document.execCommand('insertHTML', false, text);
var element = document.getElementById(id);
element.scrollIntoView();
element.parentNode.removeChild(element);
}
In some cases this works just fine and in some cases it leaves a non-break space between every key press, removing the <span></span> tags only. Any ideas? I'm open to better ways of doing this if someone has suggestions. I'm a little shocked at how hard it is to make the cursor appear but then JS is new to me.
EDIT
This is the code that works:
var viewportHeight = 0;
function setViewportHeight(vph) {
viewportHeight = vph;
if(viewportHeight == 0 && vph != 0)
viewportHeight = window.innerHeight;
}
function getViewportHeight() {
if(viewportHeight == 0)
return window.innerHeight;
return viewportHeight;
}
function makeCursorVisible() {
var sel = document.getSelection(); // change the selection
var ran = sel.getRangeAt(0); // into a range
var rec = ran.getClientRects()[0]; // that we can get coordinates from
if (rec == null) {
// Can't get coords at start of blank line, so we
// insert a char at the cursor, get the coords of that,
// then delete it again. Happens too fast to see.
ran.insertNode( document.createTextNode(".") );
rec = ran.getClientRects()[0]; // try again now that there's text
ran.deleteContents();
}
var top = rec.top; // Y coord of selection top edge
var bottom = rec.bottom; // Y coord of selection bottom edge
var vph = getViewportHeight();
if (top < 0) // if selection top edge is above viewport top,
window.scrollBy(0, top); // scroll up by enough to make the selection top visible
if (bottom >= vph) // if selection bottom edge is below viewport bottom,
window.scrollBy(0, bottom-vph + 1); // scroll down by enough to make the selection bottom visible
}
The viewportHeight is more complicated than need be for a web app. For a mobile app we need to account for the keyboard so offer a method for setting the viewportHeight manually as well as the automatic setting from the window.innerHeight.

I don't know if this will work on iOS, but if the position of the cursor means that there is a Selection at that point..
function moveToSelection(){
var sel = document.getSelection(), // change the selection
ran = sel.getRangeAt(0), // into a range
rec = ran.getClientRects()[0], // that we can get co-ordinates from
dy = rec.top; // distance to move down/up
window.scrollBy( 0, dy ); // actual move
// console.log( sel, ran, rec, y ); // help debug
}
moveToSelection();
Relevant links
getSelection
getRangeAt
getClientRects
scrollBy

Related

Long html page scroll snapping

I'm developing an html page that is quite long and the design has blocks on content vertically like pages. What I want to do is when the user scrolls down the page to "snap" to a point that each page is placed correctly in centre of screen if the position of the user is within a small distance of the correct position.
We're using jquery for other things on the page so happy to use that if it has something relevant.
Any advise is of course much appreciated.
Not tested this but it should work. Refer to the comments for an explanation of how it works.
I disable the point after it has been snapped so that it isn't repeatedly snapped when attempting to scroll away from it. The leniancy variable is both the distance from the point the user must scroll before it becomes active again and the distance before a point is snapped.
var points = [250, 675, 1225, $("#someDiv")]; // y coordinates or jQuery objects you want to centre against.
var disabledPoints = []; // Points that you won't snap to (leave empty) this allows user to scroll from a point
var wMid = ($(window).height() / 2); // Pixels to centre of window
var leniancy = 100; // Number of pixels ether side of a point before it is snapped
$.each(points, function(key, value) { // Loop through points
if (value instanceof jQuery) { // If value is a jQuery object
function setObjectPos(){
var offset = value.offset();
var centerPoint = offset.top + (value.height() /2); // Calculate object centre relative to document
value.data('centerPoint', centerPoint); // Store it for later
});
value.bind('DOMSubtreeModified', setObjectPos); // If div changes update center position.
}
}
$(window).resize(function () { // If user resizes window update window mid-point variable
wMid = ($(window).height() / 2);
});
$(window).scroll(function () {
var centerPos = $(window).scrollTop() + wMid; // Position of screen center relative to document
$.each(points, function(key, value) { // Loop through points
if (value instanceof jQuery) {
value = value.data('centerPoint');
}
var offset = value - centerPos;
if(checkDistance(offset, leniancy)) {
snapToPoint(key); // Success! snap that point
return false; // Kill the loop
}
});
$.each(disabledPoints, function(key, value) { // Loop through disabled points
if (value instanceof jQuery) {
value = value.data('centerPoint');
}
var offset = value - centerPos;
if(!checkDistance(offset, leniancy)) {
enablePoint(key); // Success! enable that point
}
});
});
function checkDistance(offset, margin) {
if (offset > 0) {
if (offset <= margin) {
return true;
}
} else {
if (offset >= (margin * -1)) {
return true;
}
}
return false;
}
function snapToPoint(index) {
var offset = (points[index] instanceof jQuery) ? points[index].data('centerPoint') - wMid : points[index] - wMid;
$(window).scrollTop(offset);
var point = points.splice(index, 1);
disabledPoints.push(point[0]);
}
function enablePoint(index) {
var point = disabledPoints.splice(index, 1);
points.push(point[0]);
}​

Find element that's on the middle of the visible screen (viewport), on scroll

I need to know if there is a way I can determine if a div is in the center of the screen.
HTML:
<div id="container">
<div class="box" id="box0">
text
</div>
<div class="box" id="box1">
text
</div>
.....
<div class="box" id="box100">
text
</div>
</div>
Is there a way to determine when a div will be in the center of the visible screen, considering that the page is scrollable? So basically, when the user is scrolling down the page, the div that's in the middle of the visible screen should be selected.
Thanks
DEMO PAGE
var findMiddleElement = (function(docElm){
var viewportHeight = docElm.clientHeight,
// here i'm using pre-cached DIV elements, but you can use anything you want.
// Cases where elements are generated dynamically are more CPU intense ofc.
elements = $('div');
return function(e){
var middleElement;
if( e && e.type == 'resize' )
viewportHeight = docElm.clientHeight;
elements.each(function(){
var pos = this.getBoundingClientRect().top;
// if an element is more or less in the middle of the viewport
if( pos > viewportHeight/2.5 && pos < viewportHeight/1.5 ){
middleElement = this;
return false; // stop iteration
}
});
console.log(middleElement);
}
})(document.documentElement);
$(window).on('scroll resize', findMiddleElement);
Another way (for modern browsers only) is to use the intersection API
This is nice method: elementFromPoint()
You can get the element in the center as so:
var elem = document.elementFromPoint($(window).width()/2, $(window).height()/2);
//if you want jquery element you can get it.
var jqueryElem = $(elem);
The height of the window and the scrollTop() of the window will give you the range of offsets that exist in the users view:
var minHeight = $(window).scrollTop()
var maxHeight = $(window).height()
var middleHeight = (maxHeight + minHeight) / 2;
You could try using a viewport selector such as:
http://www.appelsiini.net/projects/viewport
This will give you all visible elements. A plugin isn't needed but will ease the selection
var visibleElements = $("div.box").filter(":in-viewport")
From this selection you can then look for the element closest to the middleHeight:
var $midElement;
var distance = null;
var currDistance = 0;
visibleElements.each(function(index, element) {
currDistance = Math.abs(middleHeight - $midElement.offset().top);
if ( distance == null || currDistance < distance ) {
$midElement = $(element);
distance = currDistance;
}
});
Haven't tested this but it should be along the right track.

JavaScript: Scroll to selection after using textarea.setSelectionRange in Chrome

A JavaScript function selects a certain word in a textarea using .setSelectionRange().
In Firefox, the textarea automatically scrolls down to show the selected text. In Chrome (v14), it does not. Is there a way to get Chrome to scroll the textarea down to the newly selected text?
jQuery solutions are welcome.
Here is a simple and efficient solution in pure JavaScript:
// Get the textarea
var textArea = document.getElementById('myTextArea');
// Define your selection
var selectionStart = 50;
var selectionEnd = 60;
textArea.setSelectionRange(selectionStart, selectionEnd);
// Mow let’s do some math.
// We need the number of characters in a row
var charsPerRow = textArea.cols;
// We need to know at which row our selection starts
var selectionRow = (selectionStart - (selectionStart % charsPerRow)) / charsPerRow;
// We need to scroll to this row but scrolls are in pixels,
// so we need to know a row's height, in pixels
var lineHeight = textArea.clientHeight / textArea.rows;
// Scroll!!
textArea.scrollTop = lineHeight * selectionRow;
Put this in a function, extend the prototype of JavaScript's Element object with it, and you're good.
A lot of answers, but the accepted one doesn't consider line breaks, Matthew Flaschen didn't add the solution code, and naXa answer has a mistake. The simplest solution code is:
textArea.focus();
const fullText = textArea.value;
textArea.value = fullText.substring(0, selectionEnd);
textArea.scrollTop = textArea.scrollHeight;
textArea.value = fullText;
textArea.setSelectionRange(selectionStart, selectionEnd);
You can see how we solved the problem in ProveIt (see highlightLengthAtIndex). Basically, the trick is to truncate the textarea, scroll to the end, then restore the second part of the text. We also used the textSelection plugin for consistent cross-browser behavior.
Valeriy Katkov's elegant solution works great but has two problems:
It does not work for long strings
Selected contents are scrolled to the bottom of the textarea, making it hard to see the context which surrounds the selection
Here's my improved version that works for long strings (tested with at least 50,000 words) and scroll selection to the center of the textarea:
function setSelectionRange(textarea, selectionStart, selectionEnd) {
// First scroll selection region to view
const fullText = textarea.value;
textarea.value = fullText.substring(0, selectionEnd);
// For some unknown reason, you must store the scollHeight to a variable
// before setting the textarea value. Otherwise it won't work for long strings
const scrollHeight = textarea.scrollHeight
textarea.value = fullText;
let scrollTop = scrollHeight;
const textareaHeight = textarea.clientHeight;
if (scrollTop > textareaHeight){
// scroll selection to center of textarea
scrollTop -= textareaHeight / 2;
} else{
scrollTop = 0;
}
textarea.scrollTop = scrollTop;
// Continue to set selection range
textarea.setSelectionRange(selectionStart, selectionEnd);
}
It works in Chrome 72, Firefox 65, Opera 58, and Edge 42.
For an example of using this function, see my GitHub project SmartTextarea.
This is a code inspired by the Matthew Flaschen's answer.
/**
* Scroll textarea to position.
*
* #param {HTMLInputElement} textarea
* #param {Number} position
*/
function scrollTo(textarea, position) {
if (!textarea) { return; }
if (position < 0) { return; }
var body = textarea.value;
if (body) {
textarea.value = body.substring(0, position);
textarea.scrollTop = position;
textarea.value = body;
}
}
Basically, the trick is to truncate the textarea, scroll to the end, then restore the second part of the text.
Use it as follows
var textarea, start, end;
/* ... */
scrollTo(textarea, end);
textarea.focus();
textarea.setSelectionRange(start, end);
Based on the idea from naXa and Valeriy Katkov, I refined the function with fewer bugs. It should work out of the box (It's written with TypeScript. For JavaScript, just remove the type declaration):
function scrollTo(textarea: HTMLTextAreaElement, offset: number) {
const txt = textarea.value;
if (offset >= txt.length || offset < 0)
return;
// Important, so that scrollHeight will be adjusted
textarea.scrollTop = 0;
textarea.value = txt.substring(0, offset);
const height = textarea.scrollHeight;
textarea.value = txt;
// Margin between selection and top of viewport
textarea.scrollTop = height - 40;
}
Usage:
let textarea, start, end;
/* ... */
scrollTo(textarea, start);
textarea.focus();
textarea.setSelectionRange(start, end);
Complete code for Chrome:
<script type="text/javascript">
var SAR = {};
SAR.find = function () {
debugger;
var parola_cercata = $("#text_box_1").val(); // The searched word
// Make text lowercase if search is
// supposed to be case insensitive
var txt = $('#remarks').val().toLowerCase();
parola_cercata = parola_cercata.toLowerCase();
// Take the position of the word in the text
var posi = jQuery('#remarks').getCursorPosEnd();
var termPos = txt.indexOf(parola_cercata, posi);
if (termPos !== -1) {
debugger;
var target = document.getElementById("remarks");
var parola_cercata2 = $("#text_box_1").val();
// Select the textarea and the word
if (target.setSelectionRange) {
if ('selectionStart' in target) {
target.selectionStart = termPos;
target.selectionEnd = termPos;
this.selectionStart = this.selectionEnd = target.value.indexOf(parola_cercata2);
target.blur();
target.focus();
target.setSelectionRange(termPos, termPos + parola_cercata.length);
}
} else {
var r = target.createTextRange();
r.collapse(true);
r.moveEnd('character', termPos + parola_cercata);
r.moveStart('character', termPos);
r.select();
}
} else {
// Not found from cursor pos, so start from beginning
termPos = txt.indexOf(parola_cercata);
if (termPos !== -1) {
var target = document.getElementById("remarks");
var parola_cercata2 = $("#text_box_1").val();
// Select the textarea and the word
if (target.setSelectionRange) {
if ('selectionStart' in target) {
target.selectionStart = termPos;
target.selectionEnd = termPos;
this.selectionStart = this.selectionEnd = target.value.indexOf(parola_cercata2);
target.blur();
target.focus();
target.setSelectionRange(termPos, termPos + parola_cercata.length);
}
} else {
var r = target.createTextRange();
r.collapse(true);
r.moveEnd('character', termPos + parola_cercata);
r.moveStart('character', termPos);
r.select();
}
} else {
alert("not found");
}
}
};
$.fn.getCursorPosEnd = function () {
var pos = 0;
var input = this.get(0);
// IE support
if (document.selection) {
input.focus();
var sel = document.selection.createRange();
pos = sel.text.length;
}
// Firefox support
else if (input.selectionStart || input.selectionStart === '0')
pos = input.selectionEnd;
return pos;
};
</script>
I published an answer here:
http://blog.blupixelit.eu/scroll-textarea-to-selected-word-using-javascript-jquery/
It works perfectly with just one needed rule: Set a line-height in the CSS content of the textarea!
It calculate the position of the word to scroll to just by doing some simple mathematical calculation and it worked perfectly in all my experiments!

The tooltip opens at the same place irrespective of the layout area in Firefox

I have a Tooltip which on mouse over opens on the top left side corner
of the page irrespectve of the 'mouseover'.
In case of IE8 this works fine and opens just above the place where mouse is
placed but in Firefox its unable to calculate/interprete the values.
here is the code snippet for tooltip
function DoFlyOver()
{
if( ToClear != -1 ) window.clearTimeout( ToClear );
var thisForm = eval("document." + formName);
if (FlyOverArea == null) FlyOverArea = document.getElementById("FlyOverArea");
if (FlyOverArea.firstChild!==null)
FlyOverArea.removeChild(FlyOverArea.firstChild); // remove all existing content
FlyOverArea.appendChild(document.createTextNode(FoText));
FoLeft = $displayOptions.getFlyoverLeftOfCursor();
FoTop = $displayOptions.getFlyoverTopOfCursor();
FlyOverArea.style.left = Number(thisForm.mousex.value) + Number(thisForm.scrollx.value) - Number(FoLeft);
FlyOverArea.style.top = Number(thisForm.mousey.value) + Number(thisForm.scrolly.value) - Number(FoTop);
var maxX = (window.screen.width * $displayOptions.getFlyoverThreshold())/100;
// If the mouse is at the extreme right corner the max threshold should the tooltip be //placed.
if(FlyOverArea.style.posLeft > (window.screen.width - maxX)){
FlyOverArea.style.left = window.screen.width - maxX;
}
FlyOverArea.style.display = "";
ToClear = setTimeout( "ClearFlyOver()", $displayOptions.getFlyoverVisibleTimeWithCursor(), "JAVASCRIPT" );//set timeout
}
<DIV ID=FlyOverArea CLASS="FO" STYLE="display: none">
</DIV>
I suspect its wih style.top and style.left and tried with style.pixelLeft,style.posleft too but no use
Ha I found the answer,Mozilla cannot recognizes numbers but if you append with px
it will set the position. For example
var theLeft=Number(thisForm.mousex.value) + Number(thisForm.scrollx.value) - Number(FoLeft);
FlyOverArea.style.left = theLeft+"px"
This solved the problem

How to determine if hidden/overflow text is at top or bottom of element

I'd like to expand on Shog9's answer in
How to determine from javascript if an html element has overflowing content
And I'd like to know if the text that is hidden is at the top or at the bottom (or both or none) of the containing element.
What's the best way to go about that?
You can combine scrollLeft and scrollTop with Shog's answer.
Specifically:
// Author: Shog9
// Determines if the passed element is overflowing its bounds,
// either vertically or horizontally.
// Will temporarily modify the "overflow" style to detect this
// if necessary.
// Modified to check if the user has scrolled right or down.
function checkOverflow(el)
{
var curOverflow = el.style.overflow;
if ( !curOverflow || curOverflow === "visible" )
el.style.overflow = "hidden";
var isOverflowing = el.clientWidth < el.scrollWidth
|| el.clientHeight < el.scrollHeight;
// check scroll location
var isScrolledRight = el.scrollLeft > 0;
var isScrolledDown = el.scrollTop > 0;
el.style.overflow = curOverflow;
return isOverflowing;
}
I could not see the forest through the trees. Joel's code snippet var isScrolledDown = el.scrollTop > 0; made me realize how to do it. I used two functions:
function HasTopOverflow(el) {
return el.scrollTop;
}
function HasBottomOverflow(el) {
var scrollTop = el.scrollTop,
clientHeight = el.clientHeight,
scrollHeight = Math.max(el.scrollHeight, clientHeight);
return (scrollTop + clientHeight) < scrollHeight;
}
Haven't tested if it'll work on IE6+ yet, but FF works.
If there are any bugs, please let me know.

Categories

Resources