hi everyone!
i have a map with dots MAP which every 3 seconds shows a block with info
a function that is already in progress, and I want the function to stop when clicking on a point and display an infoblock for me(and i did it).
sorry in advance below is my code
// map with dots
var isActive = 0;
var isLoading = 1;
const count = document.querySelectorAll("[data-id]");//circle svg around dot
function removeClass() {
let infoCards = document.querySelectorAll("[data-info-id]");// info page name of the project
infoCards.forEach((el) => {
el.classList.remove("show");
});
}
function removeCircle() {
count.forEach((el) => {
el.style.display = "none";
});
}
function ready() {
function setAround(percent, idx) {
removeCircle();
let beforeElemIdx = idx === 0 ? count.length - 1 : idx - 1;
let beforeElem = document.querySelector(
'[data-id="' + beforeElemIdx + '"]'
);
let elem = document.querySelector('[data-id="' + idx + '"]');
elem.style.display = "block";
elem.classList.remove('active-circle');
beforeElem.style.display = "block";
const math = 2 * Math.PI * elem.r.baseVal.value;
elem.style.strokeDasharray = `${math} 1000`;
let a = math * (1 - percent / 100);
elem.style.strokeDashoffset = a;
if (percent >= 99.5) {
removeClass();
let infoShow = document.querySelector(`[data-info-id="${idx}"]`);
infoShow.classList.add("show");
isLoading++;
if (isLoading === count.length) {
isLoading = 0;
}
}
}
requestAnimationFrame(draw);
function draw(t) {
let idx = isLoading;
requestAnimationFrame(draw);
setAround((t / 30) % 100, idx);//timer 3sec
}
}
document.addEventListener("DOMContentLoaded", ready);
and i did this
var dots = document.querySelectorAll(".dota");
var infoCards = document.querySelectorAll("[data-info-id]");
let circle = document.querySelectorAll('[data-id]');
dots.forEach((el) => {
el.addEventListener('click', () => {
let idx = el.dataset.dota;
let circle = el.dataset.dota;
showInfo(idx);
addCircle(idx);
});
});
function showInfo(idx) {
removeClass();
let elem = document.querySelector(`[data-info-id='${idx}']`);
elem.classList.add('show');
}
function addCircle(idx) {
let circle = document.querySelector(`[data-id='${idx}']`);
circle.classList.add('active-circle');
}
and if u want my site pls dm me i'll send my github page
PUG CODE
TY ALL!
Have you tried making a condition into the drawing function that pauses it?
If it's paused you can call another function that will draw the info of the specific dot only once and then create a condition in which it will resume the drawing normally.
When I used drawing function I've simply added a bool variable that stored if paused or not in the recursive function.
I am trying to reach a horizontal timeline like this:
https://codepen.io/ritz078/pen/LGRWjE
My problem is, that I've got no real dates (DD/MM/YYYY) but only years like 1998-2002 or just 2009. So there is a problem, I am struggling to fix, which ends up like this:
So my aims are:
Set a fixed distance between elements
Make it work just with years
When on a device less than 768px wide ensure a single element is displayed and it appears in the center
This is my solution for 3., but the other things, I couldnt solute:
if ($(window).width() < 768 {
eventsMinDistance = $('.cd-horizontal-timeline .events-wrapper').width(/2;)
}
(timelines.length > 0) && initTimeline(timelines);
$(window).resize(function(){
if ($(window).width() < 768 {
eventsMinDistance = $('.cd-horizontal-timeline .events-wrapper').width(/2;)
} else{
eventsMinDistance = 155;
}
}
Do you guys know how do adjust it, as I am struggling since hours without any success. Thank you very much for your help!
the first part of your code works fine, but it was faulty on the syntax, I fixed it and use it and also add a couple of changes to acomplished the desired fixed width amount between each date.
Actually it was pretty easy, when you debug and you know exactly what the developer wants to do.
This is the code I wrote:
jQuery(document).ready(function($){
var timelines = $('.cd-horizontal-timeline'),
eventsRelativeDistance = false;
if ($(window).width() < 768) {
eventsMinDistance = Number($('.cd-horizontal-timeline .events-wrapper').width())/2;
}else{
var eventsMinDistance = 100;
}
(timelines.length > 0) && initTimeline(timelines);
$(window).resize( function(){
if ($(window).width() < 768) {
eventsMinDistance = Number($('.cd-horizontal-timeline .events-wrapper').width())/2;
} else{
eventsMinDistance = 100;
}
});
function initTimeline(timelines) {
timelines.each(function(){
var timeline = $(this),
timelineComponents = {};
//cache timeline components
timelineComponents['timelineWrapper'] = timeline.find('.events-wrapper');
timelineComponents['eventsWrapper'] = timelineComponents['timelineWrapper'].children('.events');
timelineComponents['fillingLine'] = timelineComponents['eventsWrapper'].children('.filling-line');
timelineComponents['timelineEvents'] = timelineComponents['eventsWrapper'].find('a');
timelineComponents['timelineDates'] = parseDate(timelineComponents['timelineEvents']);
timelineComponents['eventsMinLapse'] = minLapse(timelineComponents['timelineDates']);
timelineComponents['timelineNavigation'] = timeline.find('.cd-timeline-navigation');
timelineComponents['eventsContent'] = timeline.children('.events-content');
if(!eventsRelativeDistance){
// Set up space to store the distance in pixels.
timelineComponents['distanceInPx'] = [];
}
//assign a left postion to the single events along the timeline
setDatePosition(timelineComponents, eventsMinDistance, eventsRelativeDistance);
//assign a width to the timeline
var timelineTotWidth = setTimelineWidth(timelineComponents, eventsMinDistance, eventsRelativeDistance);
//the timeline has been initialize - show it
timeline.addClass('loaded');
//detect click on the next arrow
timelineComponents['timelineNavigation'].on('click', '.next', function(event){
event.preventDefault();
updateSlide(timelineComponents, timelineTotWidth, 'next');
});
//detect click on the prev arrow
timelineComponents['timelineNavigation'].on('click', '.prev', function(event){
event.preventDefault();
updateSlide(timelineComponents, timelineTotWidth, 'prev');
});
//detect click on the a single gallery - show new gallery content
timelineComponents['eventsWrapper'].on('click', 'a', function(event){
event.preventDefault();
timelineComponents['timelineEvents'].removeClass('selected');
$(this).addClass('selected');
updateOlderEvents($(this));
updateFilling($(this), timelineComponents['fillingLine'], timelineTotWidth);
updateVisibleContent($(this), timelineComponents['eventsContent']);
});
//on swipe, show next/prev gallery content
timelineComponents['eventsContent'].on('swipeleft', function(){
var mq = checkMQ();
( mq == 'mobile' ) && showNewContent(timelineComponents, timelineTotWidth, 'next');
});
timelineComponents['eventsContent'].on('swiperight', function(){
var mq = checkMQ();
( mq == 'mobile' ) && showNewContent(timelineComponents, timelineTotWidth, 'prev');
});
//keyboard navigation
$(document).keyup(function(event){
if(event.which=='37' && elementInViewport(timeline.get(0)) ) {
showNewContent(timelineComponents, timelineTotWidth, 'prev');
} else if( event.which=='39' && elementInViewport(timeline.get(0))) {
showNewContent(timelineComponents, timelineTotWidth, 'next');
}
});
});
}
function updateSlide(timelineComponents, timelineTotWidth, string) {
//retrieve translateX value of timelineComponents['eventsWrapper']
var translateValue = getTranslateValue(timelineComponents['eventsWrapper']),
wrapperWidth = Number(timelineComponents['timelineWrapper'].css('width').replace('px', ''));
//translate the timeline to the left('next')/right('prev')
(string == 'next')
? translateTimeline(timelineComponents, translateValue - wrapperWidth + eventsMinDistance, wrapperWidth - timelineTotWidth)
: translateTimeline(timelineComponents, translateValue + wrapperWidth - eventsMinDistance);
}
function showNewContent(timelineComponents, timelineTotWidth, string) {
//go from one gallery to the next/previous one
var visibleContent = timelineComponents['eventsContent'].find('.selected'),
newContent = ( string == 'next' ) ? visibleContent.next() : visibleContent.prev();
if ( newContent.length > 0 ) { //if there's a next/prev gallery - show it
var selectedDate = timelineComponents['eventsWrapper'].find('.selected'),
newEvent = ( string == 'next' ) ? selectedDate.parent('li').next('li').children('a') : selectedDate.parent('li').prev('li').children('a');
updateFilling(newEvent, timelineComponents['fillingLine'], timelineTotWidth);
updateVisibleContent(newEvent, timelineComponents['eventsContent']);
newEvent.addClass('selected');
selectedDate.removeClass('selected');
updateOlderEvents(newEvent);
updateTimelinePosition(string, newEvent, timelineComponents);
}
}
function updateTimelinePosition(string, event, timelineComponents) {
//translate timeline to the left/right according to the position of the selected gallery
var eventStyle = window.getComputedStyle(event.get(0), null),
eventLeft = Number(eventStyle.getPropertyValue("left").replace('px', '')),
timelineWidth = Number(timelineComponents['timelineWrapper'].css('width').replace('px', '')),
timelineTotWidth = Number(timelineComponents['eventsWrapper'].css('width').replace('px', ''));
var timelineTranslate = getTranslateValue(timelineComponents['eventsWrapper']);
if( (string == 'next' && eventLeft > timelineWidth - timelineTranslate) || (string == 'prev' && eventLeft < - timelineTranslate) ) {
translateTimeline(timelineComponents, - eventLeft + timelineWidth/2, timelineWidth - timelineTotWidth);
}
}
function translateTimeline(timelineComponents, value, totWidth) {
var eventsWrapper = timelineComponents['eventsWrapper'].get(0);
value = (value > 0) ? 0 : value; //only negative translate value
value = ( !(typeof totWidth === 'undefined') && value < totWidth ) ? totWidth : value; //do not translate more than timeline width
setTransformValue(eventsWrapper, 'translateX', value+'px');
//update navigation arrows visibility
(value == 0 ) ? timelineComponents['timelineNavigation'].find('.prev').addClass('inactive') : timelineComponents['timelineNavigation'].find('.prev').removeClass('inactive');
(value == totWidth ) ? timelineComponents['timelineNavigation'].find('.next').addClass('inactive') : timelineComponents['timelineNavigation'].find('.next').removeClass('inactive');
}
function updateFilling(selectedEvent, filling, totWidth) {
//change .filling-line length according to the selected gallery
var eventStyle = window.getComputedStyle(selectedEvent.get(0), null),
eventLeft = eventStyle.getPropertyValue("left"),
eventWidth = eventStyle.getPropertyValue("width");
eventLeft = Number(eventLeft.replace('px', '')) + Number(eventWidth.replace('px', ''))/2;
var scaleValue = eventLeft/totWidth;
setTransformValue(filling.get(0), 'scaleX', scaleValue);
}
function setDatePosition(timelineComponents, min, relativeDistance) {
var distance,
distanceNorm = 0,
distancesInPx =[];
for (i = 0; i < timelineComponents['timelineDates'].length; i++) {
if (relativeDistance){
distance = daydiff(timelineComponents['timelineDates'][0], timelineComponents['timelineDates'][i]);
distanceNorm = Math.round(distance/timelineComponents['eventsMinLapse']) + 2;
}else{
distance = 5;
distanceNorm = Math.round(distance/timelineComponents['eventsMinLapse']) + 2 + distanceNorm;
// Save am array of sizes to track the distance in pixels from the left.
timelineComponents['distanceInPx'].push(distanceNorm*min);
}
timelineComponents['timelineEvents'].eq(i).css('left', distanceNorm*min+'px');
}
}
function setTimelineWidth(timelineComponents, width, relativeDistance) {
var timeSpan = 0, timeSpanNorm, totalWidth;
if(relativeDistance){
// If relative Time Distance daydiff caclulates the first date and the last one.
timeSpan = daydiff(timelineComponents['timelineDates'][0], timelineComponents['timelineDates'][timelineComponents['timelineDates'].length-1]);
timeSpanNorm = timeSpan/timelineComponents['eventsMinLapse'];
timeSpanNorm = Math.round(timeSpanNorm) + 4;
totalWidth = timeSpanNorm*width;
}else{
// However if no relative Distance we obtain the amount of distance in pixels from the last position of the array which is the farthest element on the array.
totalWidth = timelineComponents['distanceInPx'][timelineComponents['distanceInPx'].length-1];
}
timelineComponents['eventsWrapper'].css('width', totalWidth+'px');
updateFilling(timelineComponents['eventsWrapper'].find('a.selected'), timelineComponents['fillingLine'], totalWidth);
updateTimelinePosition('next', timelineComponents['eventsWrapper'].find('a.selected'), timelineComponents);
return totalWidth;
}
function updateVisibleContent(event, eventsContent) {
var eventDate = event.data('date'),
visibleContent = eventsContent.find('.selected'),
selectedContent = eventsContent.find('[data-date="'+ eventDate +'"]'),
selectedContentHeight = selectedContent.height();
if (selectedContent.index() > visibleContent.index()) {
var classEnetering = 'selected enter-right',
classLeaving = 'leave-left';
} else {
var classEnetering = 'selected enter-left',
classLeaving = 'leave-right';
}
selectedContent.attr('class', classEnetering);
visibleContent.attr('class', classLeaving).one('webkitAnimationEnd oanimationend msAnimationEnd animationend', function(){
visibleContent.removeClass('leave-right leave-left');
selectedContent.removeClass('enter-left enter-right');
});
eventsContent.css('height', selectedContentHeight+'px');
}
function updateOlderEvents(event) {
event.parent('li').prevAll('li').children('a').addClass('older-gallery').end().end().nextAll('li').children('a').removeClass('older-gallery');
}
function getTranslateValue(timeline) {
var timelineStyle = window.getComputedStyle(timeline.get(0), null),
timelineTranslate = timelineStyle.getPropertyValue("-webkit-transform") ||
timelineStyle.getPropertyValue("-moz-transform") ||
timelineStyle.getPropertyValue("-ms-transform") ||
timelineStyle.getPropertyValue("-o-transform") ||
timelineStyle.getPropertyValue("transform");
if( timelineTranslate.indexOf('(') >=0 ) {
var timelineTranslate = timelineTranslate.split('(')[1];
timelineTranslate = timelineTranslate.split(')')[0];
timelineTranslate = timelineTranslate.split(',');
var translateValue = timelineTranslate[4];
} else {
var translateValue = 0;
}
return Number(translateValue);
}
function setTransformValue(element, property, value) {
element.style["-webkit-transform"] = property+"("+value+")";
element.style["-moz-transform"] = property+"("+value+")";
element.style["-ms-transform"] = property+"("+value+")";
element.style["-o-transform"] = property+"("+value+")";
element.style["transform"] = property+"("+value+")";
}
//based on http://stackoverflow.com/questions/542938/how-do-i-get-the-number-of-days-between-two-dates-in-javascript
function parseDate(events) {
var dateArrays = [];
events.each(function(){
var singleDate = $(this),
dateComp = singleDate.data('date').split('T');
if( dateComp.length > 1 ) { //both DD/MM/YEAR and time are provided
var dayComp = dateComp[0].split('/'),
timeComp = dateComp[1].split(':');
} else if( dateComp[0].indexOf(':') >=0 ) { //only time is provide
var dayComp = ["2000", "0", "0"],
timeComp = dateComp[0].split(':');
} else { //only DD/MM/YEAR
var dayComp = dateComp[0].split('/'),
timeComp = ["0", "0"];
}
var newDate = new Date(dayComp[2], dayComp[1]-1, dayComp[0], timeComp[0], timeComp[1]);
dateArrays.push(newDate);
});
return dateArrays;
}
function daydiff(first, second) {
return Math.round((second-first));
}
function minLapse(dates) {
//determine the minimum distance among events
var dateDistances = [];
for (i = 1; i < dates.length; i++) {
var distance = daydiff(dates[i-1], dates[i]);
dateDistances.push(distance);
}
return Math.min.apply(null, dateDistances);
}
/*
How to tell if a DOM element is visible in the current viewport?
http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport
*/
function elementInViewport(el) {
var top = el.offsetTop;
var left = el.offsetLeft;
var width = el.offsetWidth;
var height = el.offsetHeight;
while(el.offsetParent) {
el = el.offsetParent;
top += el.offsetTop;
left += el.offsetLeft;
}
return (
top < (window.pageYOffset + window.innerHeight) &&
left < (window.pageXOffset + window.innerWidth) &&
(top + height) > window.pageYOffset &&
(left + width) > window.pageXOffset
);
}
function checkMQ() {
//check if mobile or desktop device
return window.getComputedStyle(document.querySelector('.cd-horizontal-timeline'), '::before').getPropertyValue('content').replace(/'/g, "").replace(/"/g, "");
}
});
Did you ever get this to work?? Have come across the same problem. If i change the default distance from 60 to 1 the gap between is not enough and the whole timeline breaks. The calculations for the distance as described in the article is as follows
First of all, in the main.js file, we set a minimum distance between two consecutive dates, using the eventsMinDistance variable; in our case, we set eventsMinDistance = 60 (so the minimum distance will be 60px). Then we evaluate all the differences between a date and the following one; to do that we use the data-date attribute added to each date. The minimum difference is then used as a reference to evaluate the distances between two consecutive dates.
For example, let's suppose the minimum found difference is 5 days; that means that the distance, along the timeline, between two dates separated by a lapse of 5days will be 60px, while the one between two events separated by a lapse of 10 days will be 120px.
source:: www.codyhouse.org
From what i read the min distance is here:~
var timelines = $('.cd-horizontal-timeline'),
eventsMinDistance = 60;
(timelines.length > 0) && initTimeline(timelines);
This creates a variable called timelines and sets it to the class .cd-hor... and sets the min distance to 60px. changing this to 1 breaks the application as the distance between the times are too small.
function setDatePosition(timelineComponents, min) {
for (i = 0; i < timelineComponents['timelineDates'].length; i++) {
var distance = daydiff(timelineComponents['timelineDates'][0], timelineComponents['timelineDates'][i]),
distanceNorm = Math.round(distance/timelineComponents['eventsMinLapse']) + 2;
timelineComponents['timelineEvents'].eq(i).css('left', distanceNorm*min+'px');
}
}
I might be wrong but here is the section calculating the distance and the last distanceNorm*min+'px'. min i believe is 60 and distance norm is the calculation of the 5 days etc. if you remove distance norm the app breaks so you cant just add min+'px' this is because you remove the original distance.
All distances are calculated from the first timeline. Thats why the app breaks. Need to find the code that tells which to calc from which is the
var distance = daydiff(timelineComponents['timelineDates'][0], timelineComponents['timelineDates'][i]),
distanceNorm = Math.round(distance/timelineComponents['eventsMinLapse']) + 2;
this sets distance as the first element of the index and then for each timelineDates within the loop [i].
I've been at it for days but have come to the conclusion the tutorial is good as is but it is not a library and should be used as reference to write your own from scratch which is what i am now doing.
If you did ever manage it though please update as so far i managed to manipulate the dates to get equal spacing but if you insert too many it breaks which is no good hence the rewrite!
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>
I am using the removeChild method for the first time. I have use javascript to modify my navbar so that it changes to fixed position and and scroll with the user. This causes the content of the body div to jump up slightly when this happens. As a result, I have managed to insert a red box (it will later be white) to take up the extra space when the navbar's position changes.
I need that red box to be removed when the user scrolls back to the top but I can't seem to get the remove child function to fire. If somebody could take a look and point me in the right direction that would be swell!
code (relevant code section is in bold):
var fillerState = false;
// fixed positioning on scroll property for taskbar:
window.addEventListener('scroll', function (evt) {
var distance_from_top = document.body.scrollTop;
if (distance_from_top <= 80) {
document.getElementById("navBar").style.position = "static";
document.getElementById("navBarList").style.borderBottom = "solid black 4px";
document.getElementById("navBar").style.borderTop = "initial";
var myCollection = document.getElementsByClassName("navBarLink");
var collectionLength = myCollection.length;
for(var i = 0; i < collectionLength; i++){
myCollection[i].style.borderTopLeftRadius = "1em";
myCollection[i].style.borderTopRightRadius = "1em";
myCollection[i].style.borderBottomLeftRadius = "initial";
myCollection[i].style.borderBottomRightRadius = "initial";
}
// stops loads of boxes from forming:
**if(fillerState == true){
var parentRemove = document.getElementById("bodyDiv");
var fillerBoxRemove = document.getElementById("fillerBox");
parentRemove.removeChild(fillerBoxRemove);
fillerState = false;
alert(fillerState);**
}
}
else if(distance_from_top > 80) {
document.getElementById("navBar").style.position = "fixed";
document.getElementById("navBar").style.top = "0px";
document.getElementById("navBar").style.borderTop = "solid black 4px";
document.getElementById("navBarList").style.borderBottom = "initial";
var myCollection = document.getElementsByClassName("navBarLink");
var collectionLength = myCollection.length;
if(fillerState == false){
// sets filler element so that the page doesn't bounce:
var filler = document.createElement("div");
filler.style.width = "200px";
filler.style.height = "80px";
filler.style.backgroundColor = "red";
filler.style.id = "fillerBox";
//defines where the new element will be placed:
var parent = document.getElementById("bodyDiv");
var brother = document.getElementById("leftColumn");
parent.insertBefore(filler,brother);
fillerState = true;
}
for(var i = 0; i < collectionLength; i++){
myCollection[i].style.borderTopLeftRadius = "initial";
myCollection[i].style.borderTopRightRadius = "initial";
myCollection[i].style.borderBottomLeftRadius = "1em";
myCollection[i].style.borderBottomRightRadius = "1em";
}
}
});
as squint pointed out, when you're making the element, you're setting it's style.id, which is not right.
Change:
filler.style.id = "fillerBox";
To:
filler.id = "fillerBox";
And your code will work.
Alternatively, you can do as others have suggested and create the box in the html itself, set it to a class that has no display, then change it's class. Not only easier, but also stops you from creating and destroying. less resource intensive that way.
I'm using JavaScript to change CSS values to make a particular div fill the page when a button is clicked. But I would like make the change from small to filling the screen smooth. How do I do this with CSS or Javascript? This is currently how I'm changing the size of that div
function fullscreen(){ // called when button is clicked
var d = document.getElementById('viewer').style;
if(!isFullscreen){ // if not already fullscreen change values to fill screen
d.width = "100%";
d.height="100%";
d.position= "absolute";
d.left="0%";
d.top="0%";
d.margin="0 0 0 0";
isFullscreen = true;
}else{ // minimizie it
d.width="600px";
d.height="400px";
d.margin="0 auto";
d.position="relative";
isFullscreen = false;
}
}
How do I code the change from the full screen values to the minimized values to be a smooth transition instead of instantaneous?
Use jQuery'sanimate() function!
For example:
function fullscreen(){ // called when button is clicked
var o = {} // options
var speed = "fast"; // You can specify another value
if(!isFullscreen){ // if not already fullscreen change values to fill screen
o.width = "100%";
o.height="100%";
o.left="0%";
o.top="0%";
o.margin="0 0 0 0";
$("#viewer").animate(o,speed);
isFullscreen = true;
}else{ // minimize it
o.width="600px";
o.height="400px";
o.margin="0 auto";
$("#viewer").animate(o,speed);
isFullscreen = false;
}
}
You can do this by using Jquery, .animate() API see the reference .animate()
I have created a small demo using .animate() click the Demo to see the example.
What you want to do is rather complicated, first you need to get the absolute position and dimension of your element in the document, also the dimension of the document itself, there is no native cross-platform javascript functions for that but there are known techniques to find out those values, do a search. So assuming you will implement these functions yourself: getAbsoluteLeft(), getAbsoluteTop(), getWidth(), getHeight(), getDocWidth() and getDocHeight() here is the animating code (not tested):
function fullscreen(){ // called when button is clicked
var e = document.getElementById('viewer');
var d = e.style;
if(!isFullscreen){ // if not already fullscreen change values to fill screen
var duration = 1000 //milliseconds
var framesPerSecond = 24;
var beginLeft = getAbsoluteLeft( e );
var beginTop = getAbsoluteTop( e );
var beginRight = beginLeft + getWidth( e );
var beginBottom = beginTop + getHeight( e );
var endLeft = 0;
var endTop = 0;
var endRight = getDocWidth();
var endBottom = getDocHeight();
var totalFrames = duration / (1000/framesPerSecond);
var frameNo = 0;
var leftStep = (beginLeft - endLeft) / totalFrames;
var topStep = (beginTop - endTop) / totalFrames;
var rightStep = (endRight - beginRight) / totalFrames;
var bottomStep = (endBottom - beginBottom) / totalFrames;
var func = function () {
var left = beginLeft - leftStep * frameNo;
var top = beginTop - topStep * frameNo;
d.left = left+'px';
d.top = top+'px';
d.width = (beginRight + rightStep * frameNo - left)+'px';
d.height = (beginBottom + bottomStep * frameNo - top)+'px';
++frameNo;
if( frameNo == totalFrames ) {
clearInterval( timer );
d.width = "100%";
d.height="100%";
d.left="0%";
d.top="0%";
isFullscreen = true;
}
}
d.position= "absolute";
d.margin="0 0 0 0";
timer = setInterval( func, 1000 / framesPerSecond );
} else { // minimizie it
d.width="600px";
d.height="400px";
d.margin="0 auto";
d.position="relative";
isFullscreen = false;
}
}