highlighting and editing text in long string - javascript

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>

Related

The fading of my javascript is speeding up at the end

The transitioning of my text is perfect except at the end of the transition it speeds up. I need my text to continue to have the same fadein time rather than speed up.
$(function () {
var string = "Through education, we can all BeSafer";
var dest = $('#fadeIn');
var c = 0;
var i = setInterval(function () {
if (c >= string.length) {
clearInterval(i);
dest.text(string);
} else {
$('<span>').text(string[c]).
appendTo(dest).hide().fadeIn(3500);
c += 1;
}
}, 80);
http://jsfiddle.net/BeSafer/r8wo2dsr/
A simple workaround - add some spaces at the end of the sentence.
var string = "Through education, we can all BeSafer ";

Optimizing jQuery plugin to truncate text based on size of container

I am not the most savey at javascript or jQuery but I know them well enough. I put together a quick plugin I can drop into any of my websites that will truncate the text of a container dynamically without actually removing the text.
So if I have a container 600x600 pixels, I want the text to fit nicely in there, and when it needs to be cut off, add ... after the last word.
I couldn't find anything that really did this, so I created the following plugin.
It works, it works really great, the only problem is it is painfully slow. Simply because if goes through each line of text.
Is there a way to optimize this at all and not loose the functionality?
// To work the parent div to class .jFit must have it's height defined.
// Stylized spans may cause problems.
$(function() {
$('.jFit').each(function() {
$(this).jFit();
});
});
var resizeTimer;
$(window).on('resize', function(e) {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function() {
$('.jFit').each(function() {
$(this).jFit(true);
});
}, 150);
});
jQuery.fn.jFit = function(resize = false) {
var parentHeight = $(this).parent().height(),
maxHeight = parentHeight,
height = 0,
char = 0,
i = 1,
str = '',
last = 0;
$(this).siblings().each(function() {
maxHeight -= $(this).outerHeight();
maxHeight -= parseInt($(this).css('margin-top'));
maxHeight -= parseInt($(this).css('margin-bottom'));
});
if (resize) {
$(this).find('.jFit-ellipsis').remove();
var span = $(this).find('.jFit-hide'),
contents = span.html();
span.remove();
$(this).append(contents);
}
while ((str = $(this).line(i))['text'] != "") {
height += str['lineHeight'];
if (maxHeight > height) {
char += str['text'].length;
last = str['text'].length - $.trim(str['text']).lastIndexOf(' ');
i++;
} else {
break;
}
}
if ($.trim(str['text']).lastIndexOf(' ')<0) {
char -= str['text'].length;
str['text'] = $(this).line(i-1 | 0)['text'];
if (str['text'].match('<br\s*\/?>$')) {
char -= str['text'].length;
char += str['text'].lastIndexOf('<');
}
last = 0;
}
if (str['text'].match('/(.|,|;|:)$/')) {
last += 1;
}
if (str['text']!='') {
$(this).html($(this).html().substring(0,char-(last))+'<span class="jFit-ellipsis">...</span><span class="jFit-hide" style="display: none;">'+$(this).html().substring(char-(last)));
$(this).append('</span>');
}
}
jQuery.fn.line = function(line) {
var dummy = this.clone().css({
top: -9999,
left: -9999,
position: 'absolute',
width: this.width()
}).appendTo(this.parent());
var text = dummy.html().match(/(<.*?span[^>]*>|<br.*?>|[\w\.\,]+[\s\.\,])/g);
var words = text.length,
lastTopOffset = 0,
lineNumber = 0,
lineHeight = 0,
ret = '',
found = false;
for (var i = 0; i < words; ++i) {
dummy.html(
text.slice(0,i).join('') +
text[i].replace(/(\S)/, '$1<span id="jFit-lh" style="line-height:inherit;"/>') +
text.slice(i+1).join('')
);
var topOffset = jQuery('#jFit-lh', dummy).offset().top;
lineHeight = $('#jFit-lh').height();
if (topOffset !== lastTopOffset) {
lineNumber += 1;
}
lastTopOffset = topOffset;
if (lineNumber === line) {
found = true;
ret += text[i];
} else {
if (found) {
break;
}
}
}
dummy.remove();
return {text:ret,lineHeight:lineHeight};
}

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!

How can I make this jQuery "shrink text" function more efficient? With binary Search?

I've built jQuery function that takes a text string and a width as inputs, then shrinks that piece of text until it's no larger than the width, like so:
function constrain(text, ideal_width){
var temp = $('.temp_item');
temp.html(text);
var item_width = temp.width();
var ideal = parseInt(ideal_width);
var smaller_text = text;
var original = text.length;
while (item_width > ideal) {
smaller_text = smaller_text.substr(0, (smaller_text.length-1));
temp.html(smaller_text);
item_width = temp.width();
}
var final_length = smaller_text.length;
if (final_length != original) {
return (smaller_text + '…');
} else {
return text;
}
}
This works fine, but because I'm calling the function on many pieces of texts, on any browser except Safari 4 and Chrome, it's really slow.
I've tried using a binary search method to make this more efficient, but what I have so far brings up a slow script dialog in my browser:
function constrain(text, ideal_width){
var temp = $('.temp_item');
temp.html(text);
var item_width = temp.width();
var ideal = parseInt(ideal_width);
var lower = 0;
var original = text.length;
var higher = text.length;
while (item_width != ideal) {
var mid = parseInt((lower + higher) / 2);
var smaller_text = text.substr(0, mid);
temp.html(smaller_text);
item_width = temp.width();
if (item_width > ideal) {
// make smaller to the mean of "lower" and this
higher = mid - 1;
} else {
// make larger to the mean of "higher" and this
lower = mid + 1;
}
}
var final_length = smaller_text.length;
if (final_length != original) {
return (smaller_text + '…');
} else {
return text;
}
}
Does anyone have an idea of what I should be doing to make this function as efficient as possible?
Thanks! Simon
The problem with your script is probably that the while condition (item_width != ideal) possibly will never abort the loop. It might not be possible to trim the input text to the exact width ideal. In this case your function will loop forever, which will trigger the slow script dialog.
To circumvent this you should stop looping if the displayed text is just small enough (aka. adding more characters would make it too big).
I used 2 divs
<div class="englober">
<div class="title"></div>
</div>
the .englober has a fixed with and overflow:hidden, white-space:nowarp.
Then using jQuery, i resize the text from the .title to fit the .englober with :
while ($(".title").width() > $(".englober").width())
{
var dFontsize = parseFloat($(".title").css("font-size"), 10);
$(".title").css("font-size", dFontsize - 1);
}

Javascript Marquee to replace <marquee> tags

I'm hopeless at Javascript. This is what I have:
<script type="text/javascript">
function beginrefresh(){
//set the id of the target object
var marquee = document.getElementById("marquee_text");
if(marquee.scrollLeft >= marquee.scrollWidth - parseInt(marquee.style.width)) {
marquee.scrollLeft = 0;
}
marquee.scrollLeft += 1;
// set the delay (ms), bigger delay, slower movement
setTimeout("beginrefresh()", 10);
}
</script>
It scrolls to the left but I need it to repeat relatively seamlessly. At the moment it just jumps back to the beginning. It might not be possible the way I've done it, if not, anyone have a better method?
Here is a jQuery plugin with a lot of features:
http://jscroller2.markusbordihn.de/example/image-scroller-windiv/
And this one is "silky smooth"
http://remysharp.com/2008/09/10/the-silky-smooth-marquee/
Simple javascript solution:
window.addEventListener('load', function () {
function go() {
i = i < width ? i + step : 1;
m.style.marginLeft = -i + 'px';
}
var i = 0,
step = 3,
space = ' ';
var m = document.getElementById('marquee');
var t = m.innerHTML; //text
m.innerHTML = t + space;
m.style.position = 'absolute'; // http://stackoverflow.com/questions/2057682/determine-pixel-length-of-string-in-javascript-jquery/2057789#2057789
var width = (m.clientWidth + 1);
m.style.position = '';
m.innerHTML = t + space + t + space + t + space + t + space + t + space + t + space + t + space;
m.addEventListener('mouseenter', function () {
step = 0;
}, true);
m.addEventListener('mouseleave', function () {
step = 3;
}, true);
var x = setInterval(go, 50);
}, true);
#marquee {
background:#eee;
overflow:hidden;
white-space: nowrap;
}
<div id="marquee">
1 Hello world! 2 Hello world! 3 Hello world!
</div>
JSFiddle
I recently implemented a marquee in HTML using Cycle 2 Jquery plugin :
http://jquery.malsup.com/cycle2/demo/non-image.php
<div class="cycle-slideshow" data-cycle-fx="scrollHorz" data-cycle-speed="9000" data-cycle-timeout="1" data-cycle-easing="linear" data-cycle-pause-on-hover="true" data-cycle-slides="> div" >
<div> Text 1 </div>
<div> Text 2 </div>
</div>
HTML5 does not support the tag, however a lot of browsers will still display the text "properly" but your code will not validate. If this isn't an issue for you, that may be an option.
CSS3 has the ability, supposedly, to have marquee text, however because anyone that knows how to do it believes it's a "bad idea" for CSS, there is very limited information that I have found online. Even the W3 documents do not go into enough detail for the hobbyist or self-teaching person to implement it.
PHP and Perl can duplicate the effect as well. The script needed for this would be insanely complicated and take up much more resources than any other options. There is also the possibility that the script would run too quickly on some browsers, causing the effect to be completely negated.
So back to JavaScript - Your code (OP) seems to be about the cleanest, simplest, most effective I've found. I will be trying this. For the seamless thing, I will be looking into a way to limit the white space between end and beginning, possibly with doing a while loop (or similar) and actually run two of the script, letting one rest while the other is processing.
There may also be a way with a single function change to eliminate the white space. I'm new to JS, so don't know off the top of my head. - I know this isn't a full-on answer, but sometimes ideas can cause results, if only for someone else.
This script used to replace the marquee tag
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$('.scrollingtext').bind('marquee', function() {
var ob = $(this);
var tw = ob.width();
var ww = ob.parent().width();
ob.css({ right: -tw });
ob.animate({ right: ww }, 20000, 'linear', function() {
ob.trigger('marquee');
});
}).trigger('marquee');
});
</script>
<div class="scroll">
<div class="scrollingtext"> Flash message without marquee tag using javascript! </div>
</div>
Working with #Stano code and some jQuery I have created a script that will replace the old marquee tag with standard div. The code will also parse the marquee attributes like direction, scrolldelay and scrollamount.
Here is the code:
jQuery(function ($) {
if ($('marquee').length == 0) {
return;
}
$('marquee').each(function () {
let direction = $(this).attr('direction');
let scrollamount = $(this).attr('scrollamount');
let scrolldelay = $(this).attr('scrolldelay');
let newMarquee = $('<div class="new-marquee"></div>');
$(newMarquee).html($(this).html());
$(newMarquee).attr('direction',direction);
$(newMarquee).attr('scrollamount',scrollamount);
$(newMarquee).attr('scrolldelay',scrolldelay);
$(newMarquee).css('white-space', 'nowrap');
let wrapper = $('<div style="overflow:hidden"></div>').append(newMarquee);
$(this).replaceWith(wrapper);
});
function start_marquee() {
let marqueeElements = document.getElementsByClassName('new-marquee');
let marqueLen = marqueeElements.length
for (let k = 0; k < marqueLen; k++) {
let space = ' ';
let marqueeEl = marqueeElements[k];
let direction = marqueeEl.getAttribute('direction');
let scrolldelay = marqueeEl.getAttribute('scrolldelay') * 100;
let scrollamount = marqueeEl.getAttribute('scrollamount');
let marqueeText = marqueeEl.innerHTML;
marqueeEl.innerHTML = marqueeText + space;
marqueeEl.style.position = 'absolute';
let width = (marqueeEl.clientWidth + 1);
let i = (direction == 'rigth') ? width : 0;
let step = (scrollamount !== undefined) ? parseInt(scrollamount) : 3;
marqueeEl.style.position = '';
marqueeEl.innerHTML = marqueeText + space + marqueeText + space;
let x = setInterval( function () {
if ( direction.toLowerCase() == 'left') {
i = i < width ? i + step : 1;
marqueeEl.style.marginLeft = -i + 'px';
} else {
i = i > -width ? i - step : width;
marqueeEl.style.marginLeft = -i + 'px';
}
}, scrolldelay);
}
}
start_marquee ();
});
And here is a working codepen
I was recently working on a site that needed a marquee and had initially used the dynamic marquee, which worked well but I couldn't have the text begin off the screen. Took a look around but couldn't find anything quite as simple as I wanted so I made my own:
<div id="marquee">
<script type="text/javascript">
let marquee = $('#marquee p');
const appendToMarquee = (content) => {
marquee.append(content);
}
const fillMarquee = (itemsToAppend, content) => {
for (let i = 0; i < itemsToAppend; i++) {
appendToMarquee(content);
}
}
const animateMarquee = (itemsToAppend, content, width) => {
fillMarquee(itemsToAppend, content);
marquee.animate({left: `-=${width}`,}, width*10, 'linear', function() {
animateMarquee(itemsToAppend, content, width);
})
}
const initMarquee = () => {
let width = $(window).width(),
marqueeContent = "YOUR TEXT",
itemsToAppend = width / marqueeContent.split("").length / 2;
animateMarquee(itemsToAppend, marqueeContent, width);
}
initMarquee();
</script>
And the CSS:
#marquee {
overflow: hidden;
margin: 0;
padding: 0.5em 0;
bottom: 0;
left: 0;
right: 0;
background-color: #000;
color: #fff;
}
#marquee p {
white-space: nowrap;
margin: 0;
overflow: visible;
position: relative;
left: 0;
}

Categories

Resources