I have these code for making a typing script inside the textarea placeholder.It works fine.But I need to execute the typeIt function When I scroll to the form div.
var txt = "Function will execute when scrolling to the section";
var modified_txt = "";
function humanize() {
return Math.round(Math.random() * (200 - 30)) + 30;
}
//Delete final character in modified string
function deleteCharacter(text) {
//return everything but the last character
text = text.substring(0, text.length - 1);
return text;
}
//Insert character_added at end of text
function addCharacter(text, character_added) {
text = text + character_added;
return text;
}
//typos[char].error is just a self reference, it is not used
var typos = {
}
var timeOut;
var txtLen = txt.length;
var char = 0;
$('textarea').attr('placeholder', '|');
function typeIt() {
modified_txt += txt.charAt(char);
$('textarea').attr('placeholder', modified_txt + '|');
if (char === txtLen) {
$('textarea').attr('placeholder', $('textarea').attr('placeholder').slice(0, -1)); // remove the '|'
return; //Stop the loop once text is completely written.
}
var test = typos[char];
if (test !== undefined) {
setTimeout(function () {
var chunk_one = test.correction(modified_txt);
modified_txt = chunk_one;
char++;
typeIt();
}, humanize());
}
//If no typos are found then move to the next character
else {
setTimeout(function () {
char++;
typeIt();
}, humanize());
}
}
$(function () {
typeIt();
});//end jquery
You can use it this way (example with your codebase):
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
<div id="2">2</div>
<div>2</div>
<div>2</div>
<div>2</div>
<script>
var oTop = $('#2').offset().top - window.innerHeight;
$(window).scroll(function () {
var pTop = $('body').scrollTop();
console.log(pTop + ' - ' + oTop); //just for your debugging
if (pTop > oTop) {
alert();
}
});
function humanize() {
return Math.round(Math.random() * (200 - 30)) + 30;
}
//Delete final character in modified string
function deleteCharacter(text) {
//return everything but the last character
text = text.substring(0, text.length - 1);
return text;
}
//Insert character_added at end of text
function addCharacter(text, character_added) {
text = text + character_added;
return text;
}
//typos[char].error is just a self reference, it is not used
var typos = {
}
var timeOut;
var txtLen = txt.length;
var char = 0;
$('textarea').attr('placeholder', '|');
function typeIt() {
modified_txt += txt.charAt(char);
$('textarea').attr('placeholder', modified_txt + '|');
if (char === txtLen) {
$('textarea').attr('placeholder', $('textarea').attr('placeholder').slice(0, -1)); // remove the '|'
return; //Stop the loop once text is completely written.
}
var test = typos[char];
if (test !== undefined) {
setTimeout(function () {
var chunk_one = test.correction(modified_txt);
modified_txt = chunk_one;
char++;
typeIt();
}, humanize());
}
//If no typos are found then move to the next character
else {
setTimeout(function () {
char++;
typeIt();
}, humanize());
}
}
$(function () {
typeIt();
});//end jquery
</script>
You need to capture and assign a event handler to the scroll event.
for demostration purpose i have taken a div with some dummy data
HTML content
<div id="scrollDiv" style="width:50px;height:50px;overflow:scroll;" >We've Worked For a lot of people and of course we've made them so happy! You can read some handpicked Word about us here We've Worked For a lot of people and of course we've made them so happy! You can read some handpicked Word about us here
We've Worked For a lot of people and of course we've made them so happy! You can read some handpicked Word about us here We've Worked For a lot of people and of course we've made them so happy! You can read some handpicked Word about us here</div>
Jquery
$('#scrollDiv').scroll(function(){
alert('scrolling');
});
Related
In a HTML/JavaScript/React/Redux web application, I have a long string (around 300kb) of natural language. It is a transcript of a recording being played back.
I need
to highlight the currently uttered word,
to recognize a word that's clicked on,
to extract selected ranges
and to replace parts of the string (when a correction to the transcript is submitted by the user).
Everything is easy when I wrap each word in its own <span>. However, this makes the number of elements unbearable for the browser and the page gets very slow.
I can think of two ways to approach this:
I could wrap each sentence in a <span> and only wrap each word of the currently played-back sentence.
I could leave the text without HTML tags, handle clicks via document.caretPositionFromPoint, but I don't know how to highlight a word.
I would welcome more ideas and thoughts on the balance between difficulty and speed.
"to recognize a word that's clicked on"
New answer
I figure that, the code in my previous answer actually had to split the huge string of text into an huge array on every on click event. After that, a linear search is performed on the array to locate the matching string.
However, this could be improved by precomputing the word array and use binary search instead of linear searching.
Now every highlighting will run in O(log n) instead of O(n)
See: http://jsfiddle.net/amoshydra/vq8y8h19/
// Build character to text map
var text = content.innerText;
var counter = 1;
textMap = text.split(' ').map((word) => {
result = {
word: word,
start: counter,
end: counter + word.length,
}
counter += word.length + 1;
return result;
});
content.addEventListener('click', function (e) {
var selection = window.getSelection();
var result = binarySearch(textMap, selection.focusOffset, compare_word);
var textNode = e.target.childNodes[0];
if (textNode) {
var range = document.createRange();
range.setStart(textNode, textMap[result].start);
range.setEnd(textNode, textMap[result].end);
var r = range.getClientRects()[0];
console.log(r.top, r.left, textMap[result].word);
// Update overlay
var scrollOffset = e.offsetY - e.clientY; // To accomondate scrolling
overlay.innerHTML = textMap[result].word;
overlay.style.top = r.top + scrollOffset + 'px';
overlay.style.left = r.left + 'px';
}
});
// Slightly modified binary search algorithm
function binarySearch(ar, el, compare_fn) {
var m = 0;
var n = ar.length - 1;
while (m <= n) {
var k = (n + m) >> 1;
var cmp = compare_fn(el, ar[k]);
if (cmp > 0) {
m = k + 1;
} else if(cmp < 0) {
n = k - 1;
} else {
return k;
}
}
return m - 1;
}
function compare_word(a, b) {
return a - b.start;
}
Original answer
I took a fork of code from this answer from aaron and implemented this:
Instead of setting a span tag on the paragraph, we could put an overlay on top of the word.
And resize and reposition the overlay when travelling to a word.
Snippet
JavaScript
// Update overlay
overlayDom.innerHTML = word;
overlayDom.style.top = r.top + 'px';
overlayDom.style.left = r.left + 'px';
CSS
Use an overlay with transparent color text, so that we can get the overlay to be of the same width with the word.
#overlay {
background-color: yellow;
opacity: 0.4;
display: block;
position: absolute;
color: transparent;
}
Full forked JavaScript code below
var overlayDom = document.getElementById('overlay');
function findClickedWord(parentElt, x, y) {
if (parentElt.nodeName !== '#text') {
console.log('didn\'t click on text node');
return null;
}
var range = document.createRange();
var words = parentElt.textContent.split(' ');
var start = 0;
var end = 0;
for (var i = 0; i < words.length; i++) {
var word = words[i];
end = start+word.length;
range.setStart(parentElt, start);
range.setEnd(parentElt, end);
// not getBoundingClientRect as word could wrap
var rects = range.getClientRects();
var clickedRect = isClickInRects(rects);
if (clickedRect) {
return [word, start, clickedRect];
}
start = end + 1;
}
function isClickInRects(rects) {
for (var i = 0; i < rects.length; ++i) {
var r = rects[i]
if (r.left<x && r.right>x && r.top<y && r.bottom>y) {
return r;
}
}
return false;
}
return null;
}
function onClick(e) {
var elt = document.getElementById('info');
// Get clicked status
var clicked = findClickedWord(e.target.childNodes[0], e.clientX, e.clientY);
// Update status bar
elt.innerHTML = 'Nothing Clicked';
if (clicked) {
var word = clicked[0];
var start = clicked[1];
var r = clicked[2];
elt.innerHTML = 'Clicked: ('+r.top+','+r.left+') word:'+word+' at offset '+start;
// Update overlay
overlayDom.innerHTML = word;
overlayDom.style.top = r.top + 'px';
overlayDom.style.left = r.left + 'px';
}
}
document.addEventListener('click', onClick);
See the forked demo: https://jsfiddle.net/amoshydra/pntzdpff/
This implementation uses the createRange API
I don't think the number of <span> elements is unbearable once they have been positioned. You might just need to minimize reflow by avoiding layout changes.
Small experiment: ~3kb of text highlighted via background-color
// Create ~3kb of text:
let text = document.getElementById("text");
for (let i = 0; i < 100000; ++i) {
let word = document.createElement("span");
word.id = "word_" + i;
word.textContent = "bla ";
text.appendChild(word);
}
document.body.appendChild(text);
// Highlight text:
let i = 0;
let word;
setInterval(function() {
if (word) word.style.backgroundColor = "transparent";
word = document.getElementById("word_" + i);
word.style.backgroundColor = "red";
i++;
}, 100)
<div id="text"></div>
Once the initial layout has finished, this renders smoothly for me in FF/Ubuntu/4+ years old laptop.
Now, if you where to change font-weight instead of background-color, the above would become unbearably slow due to the constant layout changes triggering a reflow.
Here is a simple editor that can easily handle very large string. I tried to use minimum DOM for performance.
It can
recognize a word that's clicked on
highlight the currently clicked word, or drag selection
extract selected ranges
replace parts of the string (when a correction to the transcript is submitted by the user).
See this jsFiddle
var editor = document.getElementById("editor");
var highlighter = document.createElement("span");
highlighter.className = "rename";
var replaceBox = document.createElement("input");
replaceBox.className = "replace";
replaceBox.onclick = function() {
event.stopPropagation();
};
editor.parentElement.appendChild(replaceBox);
editor.onclick = function() {
var sel = window.getSelection();
if (sel.anchorNode.parentElement === highlighter) {
clearSelection();
return;
}
var range = sel.getRangeAt(0);
if (range.collapsed) {
var idx = sel.anchorNode.nodeValue.lastIndexOf(" ", range.startOffset);
range.setStart(sel.anchorNode, idx + 1);
var idx = sel.anchorNode.nodeValue.indexOf(" ", range.endOffset);
if (idx == -1) {
idx = sel.anchorNode.nodeValue.length;
}
range.setEnd(sel.anchorNode, idx);
}
clearSelection();
range.surroundContents(highlighter);
range.detach();
showReplaceBox();
event.stopPropagation();
};
document.onclick = function(){
clearSelection();
};
function clearSelection() {
if (!!highlighter.parentNode) {
replaceBox.style.display = "none";
highlighter.parentNode.insertBefore(document.createTextNode(replaceBox.value), highlighter.nextSibling);
highlighter.parentNode.removeChild(highlighter);
}
editor.normalize(); // comment this line in case of any performance issue after an edit
}
function showReplaceBox() {
if (!!highlighter.parentNode) {
replaceBox.style.display = "block";
replaceBox.style.top = (highlighter.offsetTop + highlighter.offsetHeight) + "px";
replaceBox.style.left = highlighter.offsetLeft + "px";
replaceBox.value = highlighter.textContent;
replaceBox.focus();
replaceBox.selectionStart = 0;
replaceBox.selectionEnd = replaceBox.value.length;
}
}
.rename {
background: yellow;
}
.replace {
position: absolute;
display: none;
}
<div id="editor">
Your very large text goes here...
</div>
I would first find the clicked word via some annoying logic (Try looking here )
Then you can highlight the word simply by wrapping the exact word with a styled span as you suggested above :)
Well, I'm not really sure how you could recognise words. You may need a 3rd party software. To highlight a word, you can use CSS and span as you said.
CSS
span {
background-color: #B6B6B4;
}
To add the 'span' tags, you could use a find and replace thing. Like this one.
Find: all spaces
Replace: <span>
So I have a type writer effect, that responds to the click of the tab. Once the tab is clicked the content box changes and begins the typing effect and the corresponding tab changes as well. However if I were to click on a tab, say tab 1 then tab 2, then back to tab 1 in rapid succession or even moderate succession, the text of the content box ends abruptly and the type effect doesn't print out all the text.
$(document).ready(function () {
$('div#tab-wrapper div.myTabs').click(function () {
var tab_id = $(this).attr('data-tab');
$('div.content').removeClass('current');
$(this).addClass('current');
$("#" + tab_id).addClass('current');
typeWriterEffect(tab_id, document.getElementById(tab_id).innerHTML, 50);
});
});
var timer;
function typeWriterEffect(id, sentence, speed) {
var index = 0; //reset index
clearInterval(timer); //clear old timer
document.getElementById(id).innerHTML = ""; //clear it immediately to prevent flicker on click
timer = setInterval(function () {
var char = sentence.charAt(index);
if (char === '<') index = sentence.indexOf('>', index);
document.getElementById(id).innerHTML = sentence.substr(0, index);
index++;
if (index === sentence.length) {
clearInterval(timer);
}
}, speed);
}
I figured I could store the innerHTML text into the data() method, but that didn't seem to work or maybe I did it wrong, same results.
So anyway, here is a JSFiddle of everything I just talked about.
That maybe because you clear the global timer every time typeWriterEffect() is called. Try to use it as a local variable, declared inside the funtion.
$(document).ready(function () {
$('div#tab-wrapper div.myTabs').click(function () {
var tab_id = $(this).attr('data-tab');
$('div.content').removeClass('current');
$(this).addClass('current');
$("#" + tab_id).addClass('current');
typeWriterEffect(tab_id, document.getElementById(tab_id).innerHTML, 50);
});
});
var timer;
function typeWriterEffect(id, sentence, speed) {
var self = this;
var index = 0; //reset index
self.timer = setInterval(function () {
var char = sentence.charAt(index);
if (char === '<') index = sentence.indexOf('>', index);
document.getElementById(id).innerHTML = sentence.substr(0, index);
index++;
if (index === sentence.length) {
clearInterval(self.timer);
}
}, speed);
clearInterval(self.timer); //clear old timer
document.getElementById(id).innerHTML = ""; //clear it immediately to prevent flicker on click
}
http://jsfiddle.net/7V4NA/34/
I have a text area where I do a countdown on .keyup() but it although it does what it supposed to it lets the user to believe that there id 1 character left to type, yet the length of the text area has reached the limit. Here is my code:
<script>
var w_limit = 3000;
$(document).ready(function(){
$('#comment').keyup(function(e) {
el = $(this);
if(el.val().length >= w_limit){
el.val( el.val().substr(0, w_limit) );
} else {
$("#word-count").text(w_limit-el.val().length + ' characters left');
}
});
});
</script>
Aside from your typos, you need to always run the $("#word-count").text(stuff); part. Here's it working without the else:
var w_limit = 20;
$(document).ready(function () {
$('#comment').keyup(function (e) {
var el = $(this),
val = el.val();
if (val.length >= w_limit){
el.val( val.substr(0, w_limit) );
}
$("#word-count").text(w_limit - el.val().length + ' characters left');
});
});
DEMO: http://jsfiddle.net/eHstm/
(of course, I used a lower w_limit number for testing purposes)
i am using inputTextArea. Actually i want to limit it's max length. I am using function like
<script type="text/javascript">
function limitTextArea(element, limit) {
if (element.value.length > limit) {
element.value = element.value.substring(0, limit);
}
}
</script>
<textarea id="description" name="description"
onkeyup="limitTextArea(this, 1000);"
onkeydown="limitTextArea(this, 1000)">
</textarea>
But what is happening suppose i write a long text in the textarea. A scrollbar is shown in the textarea. But when i reach the maxlimit and then try to write a character then the textarea scrollbar move to top.
I want that once user reach it's max limit then the scrollbar as well as cursor stuck at that position.
Why it is behaving like textarea scrollbar move to top when i try to write something after max limit?
Thanks
Referred:How to impose maxlength on textArea in HTML using JavaScript
window.onload = function() {
var txts = document.getElementsByTagName('TEXTAREA')
for(var i = 0, l = txts.length; i < l; i++) {
if(/^[0-9]+$/.test(txts[i].getAttribute("maxlength"))) {
var func = function() {
var len = parseInt(this.getAttribute("maxlength"), 10);
if(this.value.length > len) {
alert('Maximum length exceeded: ' + len);
this.value = this.value.substr(0, len);
return false;
}
}
txts[i].onkeyup = func;
txts[i].onblur = func;
}
}
}
http://viralpatel.net/blogs/2008/12/set-maxlength-of-textarea-input-using-jquery-javascript.html
You can use this also to prevent the user from writing more than you want in a textarea.
Atleast this code prevents my textarea from scrolling to top
jQuery(document).ready(function($) {
$('textarea.max').keyup(function() {
var $textarea = $(this);
var max = 400;
if ($textarea.val().length > max) {
var top = $textarea.scrollTop();
$textarea.val($textarea.val().substr(0, max));
$textarea.scrollTop(top);
}
});
}); //end if ready(fn)
Here is the reference from which i got the idea to use it like this
How to prevent textarea from scrolling to the top when its value changed?
Better Solution:
jQuery(document).ready(function($) {
var max = 400;
$('textarea.max').keypress(function(event) {
if (event.which < 0x20) {
// e.which < 0x20, then it's not a printable character
// e.which === 0 - Not a character
return; // Do nothing
}
if (this.value.length == max) {
event.preventDefault();
} else if (this.value.length > max) {
// Maximum exceeded
this.value = this.value.substring(0, max);
}
}); //end of keypress(fn)
}); //end if ready(fn)
Thanks
I'm trying to have a function that does setTimeout, then changes the innerHTML:
<script type="text/javascript">
$(document).ready(function(){
setTimeout(function(){
document.getElementById("middlecolor").innerHTML='new text new text';
}, 1000);
});
</script>
Question: How could I animate the new text that appears, i.e. line by line as opposed to being written all at once?
Thanks for any suggestions!!
Try something like this:
<div id="text">
</div>
$(document).ready(function () {
var interval = setInterval(function () {
$('#text').append('<p style="display: none;">new text</p>');
$('#text p:last').fadeIn('slow');
}, 5000);
});
See the example here
If you want to kill the interval, can be doing this:
clearInterval(interval);
Greatings.
Line-by-line is a bit tricky, but possible.
var ps = document.getElementById("text").children;
var i = 0;
var $p = $(ps[i]);
setTimeout(function newline(){
$p.css("height", function(index, h){
h = parseInt(h);
h += parseInt($p.css("line-height"));
console.log(h, ps[i].scrollHeight);
if (h > ps[i].scrollHeight)
$p = $(ps[++i]);
return h;
});
if (i < ps.length)
setTimeout(newline, 200);
}, 200);
I'd suggest to use a typewriter effect, which is also very likable: http://jsfiddle.net/pZb8W/1/
var ps = document.getElementById("text").children;
var i = 0;
var $p, text;
var speed = 20;
setTimeout(function newchar(){
if (!text) {
$p = $(ps[i++]);
text = $p.text();
$p.empty().show();
}
$p.append(document.createTextNode(text.charAt(0)));
text = text.slice(1);
if (text.length || i < ps.length)
setTimeout(newchar, Math.random()*speed+speed);
}, 3*speed);
Here's a function that would animate in multiple lines of text, one after the other:
<script type="text/javascript">
$(document).ready(function(){
function animateAddText(id, text, delta) {
var lines = text.split("\n");
var lineCntr = 0;
var target = $("#" + id);
function addLine() {
if (lineCntr < lines.length) {
var nextLine = "";
if (lineCntr != 0) {
nextLine = "<br>";
}
nextLine += lines[lineCntr++];
$("<span>" + nextLine + "</span>").hide().appendTo(target).fadeIn(1000);
setTimeout(addLine, delta);
}
}
addLine();
}
var multilineText = "First line\nSecond Line\nThird Line\nFourth Line\nFifth Line";
animateAddText("middlecolor", multilineText, 1000);
});
</script>
And a working demo: http://jsfiddle.net/jfriend00/Gcg5T/