I am new to javascript and still learning. I have an svg map of a country separated in regions, and I want to display some information when hovering over each region.
The code below is working fine (jquery) when running it locally but when uploading it to Github as a Github page it isn't working.
I would like some advice on how to transfom the below part of my code into javascript. (I have tried something with addEventListener and body.appendChild but with no success)
$('#regions > *').mouseover(function (e) {
var region_data = $(this).data('region');
// Info box informations
$('<div class="info_box">' + region_data.region_name + '<br>' + '</div>').appendTo('body');
});
// Show info box when mousemove over a region
$('#regions > *').mousemove(function(e) {
var mouseX = e.pageX,
mouseY = e.pageY;
// Position of information box
$('.info_box').css({
top: mouseY-50,
left: mouseX+10
});
}).mouseleave(function () {
$('.info_box').remove();
});
I have tried something like the following :
var mapRegion = document.querySelectorAll("#regions > *");
mapRegion.forEach(function(reg){
reg.addEventListener('mouseover', function(el){
var perif_data = this.data('region');
document.body.appendChild('<div class="info_box">' + region_data.region_name + '<br>' + '</div>');
});
reg.addEventListener('mousemove', function(e){
var mouseX = e.pageX;
var mouseY = e.pageY;
// Position of information box
document.querySelector('info_box').style.top = mouseY-50;
document.querySelector('info_box').style.css = mouseX+10;
});
reg.addEventListener('mouseleave', function(){
reg.classList.remove('.info_box');
});
});
But I'm getting on console :
this.data is not a function
document.querySelector(...) is null
Modern JavaScript makes this very easy. You just need to iterate over the results of the querySelectorAll call and add the listener to each child.
Also, it looks like your data is a JSON object, so you may need to parse it using JSON.parse.
I recommend not destroying and re-creating the infobox each time. Just update it with the latest info and hide/show it depending on whether or not your are currently mousing-over a region.
Array.from(document.querySelectorAll('#regions > *')).forEach(region => {
region.addEventListener('mouseover', e => {
const infobox = document.querySelector('.info_box')
const regionData = JSON.parse(e.target.dataset.region)
infobox.textContent = regionData.region_name
infobox.classList.toggle('hidden', false)
})
region.addEventListener('mousemove', e => {
const infobox = document.querySelector('.info_box')
if (!infobox.classList.contains('hidden')) {
Object.assign(infobox.style, {
top: (e.pageY - 50) + 'px',
left: (e.pageX + 10) + 'px'
})
}
})
region.addEventListener('mouseleave', e => {
const infobox = document.querySelector('.info_box')
infobox.classList.toggle('hidden', true)
})
})
.info_box {
position: absolute;
top: 0;
left: 0;
border: thin solid grey;
background: #FFF;
padding: 0.25em;
}
.info_box.hidden {
display: none;
}
.region {
display: inline-block;
width: 5em;
height: 5em;
line-height: 5em;
text-align: center;
margin: 0.5em;
border: thin solid grey;
}
<div id="regions">
<div class="region" data-region='{"region_name":"A"}'>Section A</div>
<div class="region" data-region='{"region_name":"B"}'>Section B</div>
<div class="region" data-region='{"region_name":"C"}'>Section C</div>
</div>
<div class="info_box hidden">
</div>
You can simply this by implementing an addListeners function that loops over all the elements and applies various event listeners.
const addListeners = (selector, eventName, listener) => {
Array.from(document.querySelectorAll(selector)).forEach(el => {
typeof eventName === 'string' && listener != null
? el.addEventListener(eventName, listener)
: Object.keys(eventName).forEach(name =>
el.addEventListener(name, eventName[name]))
})
}
addListeners('#regions > *', {
mouseover: e => {
const infobox = document.querySelector('.info_box')
const regionData = JSON.parse(e.target.dataset.region)
infobox.textContent = regionData.region_name
infobox.classList.toggle('hidden', false)
},
mousemove: e => {
const infobox = document.querySelector('.info_box')
if (!infobox.classList.contains('hidden')) {
Object.assign(infobox.style, {
top: (e.pageY - 50) + 'px',
left: (e.pageX + 10) + 'px'
})
}
},
mouseleave: e => {
const infobox = document.querySelector('.info_box')
infobox.classList.toggle('hidden', true)
}
})
.info_box {
position: absolute;
top: 0;
left: 0;
border: thin solid grey;
background: #FFF;
padding: 0.25em;
}
.info_box.hidden {
display: none;
}
.region {
display: inline-block;
width: 5em;
height: 5em;
line-height: 5em;
text-align: center;
margin: 0.5em;
border: thin solid grey;
}
<div id="regions">
<div class="region" data-region='{"region_name":"A"}'>Section A</div>
<div class="region" data-region='{"region_name":"B"}'>Section B</div>
<div class="region" data-region='{"region_name":"C"}'>Section C</div>
</div>
<div class="info_box hidden">
</div>
//Get the body for Adding and removing the info_box
const body = document.querySelector("body");
//Get All Descendants of #Regions
const elements = document.querySelectorAll("#regions > *");
//Create the info_box Element
const infoBoxElement = document.createElement("div");
//Set the class
infoBoxElement.className = "info_box";
//Iterate over each descendant of Regions
elements.forEach((element) => {
//Let's add MouseOver Event
element.addEventListener("mouseover", (e) => {
//get the "data-"" of the element and Parse it
const regionData = JSON.parse(element.dataset.region);
//Let's reuse the infoBoxElement and Assign the innerHTML
infoBoxElement.innerHTML = regionData.region_name + "<br>";
//Appending the infoBoxElement to the Body
body.append(infoBoxElement);
});
//Let's add MouseMove Event
element.addEventListener("mousemove", (e) => {
const mouseX = e.pageX,
mouseY = e.pageY;
//Get the Infobox HTML element
const infoBox = document.getElementsByClassName("info_box")[0];
//Lets add the css Style
infoBox.style.top = mouseX - 50;
infoBox.style.top = mouseY + 10;
});
//Let's add MouseLeave Event
element.addEventListener("mouseleave", (e) => {
//Get the Infobox HTML element
const infoBox = document.getElementsByClassName("info_box")[0];
//Lets get rid of it
infoBox.remove();
});
});
Related
I have this kind of code in jQuery:
$('.selector').mousemove(function (e) {
$position = Math.round(e.pageX / $(window).width() * 100000) / 1000;
$('.selector').css('background-position', $position + '% center');
});
I have to transform it to Vue 3 js logic (classic way, I am not using composition api), but I am not sure how to do it and I would like to avoid to add other libraries as jquery etc.... to keep it fast and small.
My solution (Thx to kissu):
Div with parallax:
<div #mousemove="doParallaxStuff($event)" ref="parallaxDiv">... </div>
Vue js method :
doParallaxStuff(e) {
this.backgroundPosition =
Math.round((e.pageX / window.innerWidth) * 100000) / 1000;
this.$refs.parallaxDiv.setAttribute(
"style",
"background-position:" + this.backgroundPosition + "% center"
);
},
You could create a custom directive for it:
2.x syntax:
Vue.directive('moving-background', {
inserted: function(el) {
el.addEventListener('mousemove', (e) => {
const position = Math.round(e.pageX / windowWidth() * 1e5) / 1e3;
el.style.backgroundPosition = `${position}% center`;
})
}
})
new Vue({
el: '#app'
})
function windowWidth() {
const prop = window.document.documentElement.clientWidth;
const body = window.document.body;
return window.document.compatMode === "CSS1Compat" && prop ||
body && body.clientWidth ||
prop;
}
.test-element {
background: url('https://vuejs.org/images/logo.png') center no-repeat;
width: 100%;
height: 300px;
border: solid #35495e;
border-width: 10px 0;
margin: 2rem auto;
}
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.12"></script>
<div id="app">
<div v-moving-background class="test-element"></div>
</div>
3.x syntax:
const app = Vue.createApp({})
app.directive('moving-background', {
beforeMount: function(el) {
el.addEventListener('mousemove', (e) => {
const position = Math.round(e.pageX / windowWidth() * 1e5) / 1e3;
el.style.backgroundPosition = `${position}% center`;
})
}
})
app.mount('#app')
function windowWidth() {
const prop = window.document.documentElement.clientWidth;
const body = window.document.body;
return window.document.compatMode === "CSS1Compat" && prop ||
body && body.clientWidth ||
prop;
}
.test-element {
background: url('https://vuejs.org/images/logo.png') center no-repeat;
width: 100%;
height: 300px;
border: solid #35495e;
border-width: 10px 0;
margin: 2rem auto;
}
<script src="https://unpkg.com/vue#next/dist/vue.global.prod.js"></script>
<div id="app">
<div v-moving-background class="test-element"></div>
</div>
Note the windowWidth() function is a replacement for jQuery's $(window).width(), taken from here. It should be placed in a helper file and imported in your directive (and wherever else you might need it).
Now you can place v-moving-background directive on any DOM element or Vue component you want this behavior on, as shown on div.test-element above.
The CSS is not needed, I added it for this demo only.
I want to show the very hard array and hard array in the textarea. Right now, it shows under the textarea as I don't know how to show it in the textarea. The user gives the input and the server response with the hard and very hard sentences from the user input. The hard sentences have a yellow background and the very hard have red background. For now, only the hard and very hard sentences with yellow and red background respectively is shown below the textarea and not the whole thing but I think it isn't intuitive as the user would have to go and search for the sentences in the textarea again as where the sentence exactly lies. So I want the whole thing to be shown in the textarea itself with the hard and very hard sentences highlighted in yellow and red background.
Right now my code looks something like this:
state={
hardArray: [],
vhardArray: []
}
performHard = async () => {
const { enteredText } = this.state;
const body = { text: enteredText };
const stringifiedBody = JSON.stringify(body);
const options = {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json"
},
body: stringifiedBody
};
const url = "api/same";
try {
const response = await fetch(url, options);
const result = await response.json();
this.setState(prevState => ({
hardArray: [...prevState.hardArray, ...result.hard]
}));
} catch (error) {
console.error("error");
}
};
performVHard = async () => {
const { enteredText } = this.state;
const body = { text: enteredText };
const stringifiedBody = JSON.stringify(body);
const options = {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json"
},
body: stringifiedBody
};
const url ="api/same";
try {
const response = await fetch(url, options);
const result = await response.json();
this.setState(prevState => ({
vhardArray: [...prevState.vhardArray, ...result.very_hard]
}));
} catch (error) {
console.error("error");
}
};
performAnalysis = () => {
this.performHard();
this.performVHard();
};
<textarea
name="enteredText"
className="textareaclass"
placeholder="Enter your text here"
onChange={this.handleChange}
value={enteredText}
></textarea>
<Button
className="rectangle-3"
onClick={this.performAnalysis}>Analyse
</Button>
<div>
{this.state.hardArray.map((word, i) => (
<span className="hardColor">{word}</span>
))}
{this.state.vhardArray.map((word, i) => (
<span className="vhardColor">{word}</span>
))}
</div>
edit: this is how I receive the respond from the server
{
"hard": [
"It's the possibility of having a dream come true that makes life interesting",
"I cannot fix on the hour, or the spot, or the look or the words, which laid the foundation.",
]
"very_hard": [
“He stepped down, trying not to look long at her, as if she were the sun, yet he saw her, like the sun, even
without looking.”
]
}
I want to show all the text in the same textarea where the user wrote his content instead of showing anywhere else in the browser as it will make everything look ugly.
You can only give a textarea one background color, but you can make it transparent and put things behind it to add some color, yea this is a total hack and you will need to fiddle with the sizes and the blank lines to move the text down a bit - I will leave that exercise to you.
This also does not show how to get your values into the textarea but that is simple JavaScript/react code perhaps.
I altered this with some functions to illustrate where you MIGHT simply add/remove blank lines in the textarea to match the height of the background color - would probably have to adjust that background to match when this overflows the size, OR you might adjust the background to make it smaller for the colors.
I will leave it to you to determine which is the better option, I used "|" and "||" as the line/section separators as once it is in the textarea and edited you will need something like that.
All I have time for right now to enhance this but should give a starting point for this somewhat edge case without a clear standard solution.
.message {
width: 300px;
height: 150px;
display: block;
position: absolute;
}
textarea.format-custom,
.custom-underlay,
.underlay {
margin: 0;
box-sizing: border-box;
vertical-align: top;
display: block;
position: absolute;
border: lime solid 1px;
}
textarea.format-custom {
width: 100%;
height: 100%;
background: transparent;
resize: none;
display: block;
}
.underlay {
width: 100%;
height: 100%;
background: transparent;
display: block;
z-index: -1;
}
.custom-underlay {
width: 100%;
height: 50%;
margin: 0;
box-sizing: border-box;
vertical-align: top;
}
.custom-underlay.top {
background-color: #FFDDDD;
top: 0;
left: 0;
}
.custom-underlay.bottom {
background-color: #DDDDFF;
top: 50%;
left: 0;
}
<div class="message">
<label for="msg">Your message:</label>
<textarea id="msg" name="user_message" class="format-custom">howdy, I am here
bottom of here</textarea>
<div class="underlay">
<div class="custom-underlay top"></div>
<div class="custom-underlay bottom"></div>
</div>
</div>
Alternate idea from question, put text on the div's behind:
'use strict';
// borrowed code from https://stackoverflow.com/a/17590149/125981
// makeClass - By Hubert Kauker (MIT Licensed)
// original by John Resig (MIT Licensed).
var makeClass = (function(Void) {
return function() {
var constructor = function() {
var init = constructor.prototype.init,
hasInitMethod = (typeof init == "function"),
instance;
if (this instanceof constructor) {
if (hasInitMethod) init.apply(this, arguments);
} else {
Void.prototype = constructor.prototype;
instance = new Void();
if (hasInitMethod) init.apply(instance, arguments);
return instance;
}
};
return constructor;
};
})(function() {});
//make a class MyApp using above
var MyApp = makeClass();
// create MyApp functional part using the init:
MyApp.prototype.init = function(myItem, showmeClass = "showme", separator = "|", groupSeparator = "||") {
let eventChangeName = "change";
let textElement = document.getElementById(myItem);
let showme = textElement.closest(".container").getElementsByClassName(showmeClass)[0];
let lineSep = "|\n";
let myData = {
hard: [],
very_hard: []
};
this.sentData = {
hard: [],
very_hard: []
};
//so we can tell the lines
function getStyle(elId, styleProp) {
var x = document.getElementById(elId);
let y = {};
if (x.currentStyle) {
y = x.currentStyle[styleProp];
} else if (window.getComputedStyle) {
y = document.defaultView.getComputedStyle(x, null).getPropertyValue(styleProp);
}
return y;
}
function getTextareaThings(myTextarea) {
let taComputedStyles = window.getComputedStyle(myTextarea);
return {
height: myTextarea.style.height,
rows: myTextarea.rows,
clientHeight: myTextarea.clientHeight,
lineHeight: taComputedStyles.getPropertyValue('line-height'),
font: taComputedStyles.getPropertyValue('font-size')
};
}
function getLinesInString(myString) {
/* new line things: /r old Mac, /cr/lf some, /n some
all the "new line": regex: /\r\n|\n|\r/gm
above reduced regex: g and m are for global and multiline flags */
let nl = /[\r\n]+/gm;
let lines = [];
lines = myString.split(nl);
return lines;
}
function splitGroupString(myString, separator) {
let strings = [];
strings = myString.split(separator);
return strings;
}
function getGroupsInString(myString) {
return splitGroupString(myString, groupSeparator);
}
function getGroupItemsInString(myString) {
return splitGroupString(myString, separator);
}
function getCurrentValue() {
return textElement.value;
}
function addNewLines(text, count) {
let newLine = "\n";
return text + newLine.repeat(count);
}
// make stuff obvious
function onFocusTextareaValue(event) {
showForDebug(event);
}
function onChangeTextareaValue(event) {
if (event.type == eventChangeName) {
event.stopPropagation();
event.stopImmediatePropagation();
}
showForDebug(event);
}
function showForDebug(event) {
let what = "Event: " + event.type;
let b = "<br />";
let tat = getTextareaThings(event.target);
let v = getCurrentValue().replace(what, "");
showme.innerHTML = what + b + ": lines:" + getLinesInString(v).length + b + v;
}
function getStringLineCount(arr) {
arr.length;
}
function getGroupItems() {
let groups = getGroupsInString(getCurrentValue());
let groupItems = {
count: groups.length, // how many groups, two in the definition (top/bottom)
groups: []
};
groups.forEach(function(group, index, groupsArr) {
let items = getGroupItemsInString(group);
// determine how to define "group name", I use a string and the index here
let gname = "group" + index;
let g = {
itemCount: items.length // number in this group
};
g[gname] = {
items: []
};
items.forEach(function(item, itemindex, itemsArr) {
let itemName = "item" + itemindex;
let itemobj = {};
itemobj[itemName] = {
items: item
};
g[gname].items.push(itemobj);
});
groupItems.groups.push(g);
});
return groupItems;
}
// setup events
textElement.addEventListener(eventChangeName, onChangeTextareaValue, false);
textElement.addEventListener("focus", onFocusTextareaValue, false);
this.getGeometry = function() {
let geometry = {};
let element = textElement;
let rect = element.getBoundingClientRect();
geometry.top = rect.top;
geometry.right = rect.right;
geometry.bottom = rect.bottom;
geometry.left = rect.left;
geometry.offsetHeight = element.offsetHeight;
geometry.rows = element.rows;
geometry.clientHeight = element.clientHeight;
geometry.fontSize = this.getStyleProperty("font-size");
geometry.lineCount = this.getLines().length;
geometry.lineHeight = this.getLineHeight();
geometry.height = geometry.bottom - geometry.top;
geometry.width = geometry.right - geometry.left;
console.log("Geometry:",geometry);
};
this.getMetrics = function() {
let fSize = this.getStyleProperty("font-size");
let lineCount = this.getLines().length;
let lineHeight = this.getLineHeight();
let yh = lineHeight / lineCount;
let yfhPixel = parseInt(fSize, 10);
let yLineY = yh * yfhPixel;
console.log("LH:", lineHeight, "font:", fSize, "Lines:", lineCount, "lineHeight:", lineHeight, "yh:", yh, "yfPixel:", yfhPixel, "yLineY:", yLineY);
};
this.getStyleProperty = function(propertyName) {
return getStyle(textElement.id, propertyName)
};
// public functions and objects
this.getLines = function() {
return getLinesInString(getCurrentValue());
};
this.getGroups = function() {
return getGroupsInString(getCurrentValue());
};
this.setText = function(content) {
if (!content) {
content = this.sentData;
}
let hard = content.hard.join(lineSep);
let veryHard = content.very_hard.join(lineSep);
this.textElement.value = hard.concat("|" + lineSep, veryHard);
};
this.getLineHeight = function(element) {
if (!element) {
element = textElement;
}
let temp = document.createElement(element.nodeName);
temp.setAttribute("style", "margin:0px;padding:0px;font-family:" + element.style.fontFamily + ";font-size:" + element.style.fontSize);
temp.innerHTML = "test";
temp = element.parentNode.appendChild(temp);
let lineHeight = temp.clientHeight;
temp.parentNode.removeChild(temp);
return lineHeight;
};
this.getGroupItems = function() {
return getGroupItems();
};
this.textElement = textElement;
this.showme = showme;
};
let sentData = {
hard: [
"It's the possibility of having a dream come true that makes life interesting",
"I cannot fix on the hour, or the spot, or the look or the words, which laid the foundation."
],
very_hard: ["He stepped down, trying not to look long at her, as if she were the sun, yet he saw her, like the sun, even without looking."]
};
// create instances and use our app, pass the id
var containerApp = MyApp("textThing"); //default last three parameters
containerApp.sentData = sentData;
containerApp.setText();
let groups = containerApp.getGroups();
let groupItems = containerApp.getGroupItems();
containerApp.getMetrics();
containerApp.getGeometry();
// create instances and use our app, pass the id
var containerApp2 = MyApp("msgTwo", "showme", "|", "||");
//console.log("Second One Lines:", containerApp2.getLines().length);
//containerApp2.getMetrics();
//containerApp2.getGeometry();
.page-container {
display: flex;
/* center and stack the containers*/
justify-content: center;
flex-direction: column;
align-items: center;
font-size: 62.5%;
}
.container {
border: solid 1px black;
}
.container-block {
border: 2px dashed #AAAAAA;
}
.container-items {
width: 500px;
position: relative;
}
.container-items .format-custom,
.container-items label {
width: 100%;
}
.container-items .format-custom {
height: 10em
}
.message-hr {
border: 1px solid blue;
background-color: blue;
height: 0.05em;
width: 450px;
align-items: center;
margin: 0.5em;
}
.showme {
border: dotted 2px dodgerblue;
background-color: #EEEEEE;
padding: 1em;
}
textarea.format-custom,
.custom-underlay,
.underlay {
margin: 0;
box-sizing: border-box;
vertical-align: top;
display: block;
border: lime solid 1px;
}
textarea.format-custom {
width: 100%;
height: 3em;
background: transparent;
resize: none;
border: red solid 1px;
padding: 0.5em;
}
.underlay {
border: 1px solid #fff;
width: 100%;
height: 100%;
background: transparent;
top: 1em;
left: 0;
display: block;
z-index: -1;
position: absolute;
}
.custom-underlay {
width: 100%;
height: 50%;
margin: 0;
box-sizing: border-box;
vertical-align: top;
position: absolute;
}
.custom-underlay.top {
background-color: #FFFF00;
top: 0;
left: 0;
}
.custom-underlay.bottom {
background-color: #FFAAAA;
top: 50%;
left: 0;
}
<div class="page-container">
<div class="container">
<div class="container-block">
<div class="container-items">
<label for="textThing">Your message:</label>
<textarea id="textThing" name="textThing" class="format-custom">howdy, I am here|another one | cheese burgers
fries and a drink
||
bottom of here| bottom second</textarea>
<div class="underlay">
<div class="custom-underlay top"></div>
<div class="custom-underlay bottom"></div>
</div>
</div>
</div>
<div class="showme">xxxone</div>
</div>
<div class="container">
<div class="container-block">
<div class="message-hr container-items">
</div>
</div>
</div>
<div class="container">
<div class="container-block">
<div class="container-items">
<label for="msgTwo">Second message:</label>
<textarea id="msgTwo" name="msgTwo" class="format-custom">Not the same|Nxxt one
||
bottom of next</textarea>
<div class="underlay">
<div class="custom-underlay top"></div>
<div class="custom-underlay bottom"></div>
</div>
</div>
</div>
<div class="showme">xxxtwo</div>
</div>
</div>
its not with color but in this code you can set a lable like this (hard: veryhard :)
state = {
value: "",
hard: [],
veryHard: []
};
handleChange = ({ target }) => {
const { value, name } = target;
console.log(value, name);
this.setState({ value });
};
performAnalysis = () => {
let hard = [
//this state ,get from performHard function (you should set state it )
"It's the possibility of having a dream come true that makes life interesting",
"I cannot fix on the hour, or the spot, or the look or the words, which laid the foundation."
];
let veryHard = [
//this state ,get from performVHard function (you should set state it )
"“He stepped down, trying not to look long at her, as if she were the sun, yet he saw her, like the sun, even without looking.“"
];
this.setState({ hard, veryHard });
// you shoud setState like this in these 2 function
// this.performHard();
// this.performVHard();
};
render() {
return (
<div className="App">
<header className="App-header">
<textarea
value={
this.state.value.length
? this.state.value
: " Hard : " +
" " +
this.state.hard +
" " +
"very Hard : " +
this.state.veryHard
}
onChange={this.handleChange}
/>
<button onClick={this.performAnalysis}>analise</button>
</header>
</div>
);
}
its not exactly that you want,but you can get help from this code
Is there any way to force an update/run of an IntersectionObserver instance? The callback will be executed by default, when the viewport has changed. But I'm looking for a way to to execute it when other events happen, like a change of elements.
An Example:
On initialization everything works as expected. But when you change the position of the #red element, nothing happens.
// elements
let green = document.querySelector('#green');
let red = document.querySelector('#red');
// observer callback
let callback = entries => {
entries.forEach(entry => {
let isInside = entry.intersectionRatio >= 1 ? "fully" : "NOT";
console.log("#" + entry.target.id + " is " + isInside + " inside #container");
});
};
// start observer
let options = {root: document.querySelector('#container')};
let observer = new IntersectionObserver(callback, options);
observer.observe(green);
observer.observe(red);
// button action
document.querySelector('button').addEventListener('click', () => {
red.style.right = red.style.right == "" ? "0px" : "";
});
#container {
width: 100px;
height: 100px;
background: blue;
position: relative;
}
#green, #red {
width: 50px;
height: 50px;
background: green;
position: absolute;
}
#red {
background: red;
right: -10px;
}
<button>move #red</button>
<br /><br />
<div id="container">
<div id="green"></div>
<div id="red"></div>
</div>
Is there any way to make this working? Only thing that would work is to unobserve the element and start observing it again. This may be work for an single element, but not if the Observer has hundreds of elements to watch.
// elements
let green = document.querySelector('#green');
let red = document.querySelector('#red');
// observer callback
let callback = entries => {
entries.forEach(entry => {
let isInside = entry.intersectionRatio >= 1 ? "fully" : "NOT";
console.log("#" + entry.target.id + " is " + isInside + " inside #container");
});
};
// start observer
let options = {root: document.querySelector('#container')};
let observer = new IntersectionObserver(callback, options);
observer.observe(green);
observer.observe(red);
// button action
document.querySelector('button').addEventListener('click', () => {
red.style.right = red.style.right == "" ? "0px" : "";
observer.unobserve(red);
observer.observe(red);
});
#container {
width: 100px;
height: 100px;
background: blue;
position: relative;
}
#green, #red {
width: 50px;
height: 50px;
background: green;
position: absolute;
}
#red {
background: red;
right: -10px;
}
<button>move #red</button>
<br /><br />
<div id="container">
<div id="green"></div>
<div id="red"></div>
</div>
I don't think it is possible to force the intersection observer to update without calling unobserve/observe on the node, but you can do this for all observed nodes by saving them in a set:
class IntersectionObserverManager {
constructor(observer) {
this._observer = observer;
this._observedNodes = new Set();
}
observe(node) {
this._observedNodes.add(node);
this._observer.observe(node);
}
unobserve(node) {
this._observedNodes.remove(node);
this._observer.unobserve(node);
}
disconnect() {
this._observedNodes.clear();
this._observer.disconnect();
}
refresh() {
for (let node of this._observedNodes) {
this._observer.unobserve(node);
this._observer.observe(node);
}
}
}
Edit: use a Set instead of a WeakSet since they are iterable so there is no need to check if the element is being observed for each element in the body. Be carefull to call unobseve in order to avoid memory problems.
You just need to set threshold: 1.0 for your Intersection observer. This is a tricky parameter to comprehend. Threshold defines the percentage of the intersection at which the Observer should trigger the callback.
The default value is 0 which means callback will be triggered either when the very first or very last pixel of an element intersects a border of the capturing frame. Your element never completely leaves the capturing frame. This is why callback is never called.
If we set the threshold to 1 we tell the observer to trigger our callback when the element is 100% within the frame. It means the callback will be triggered on change in this state of 100% inclusiveness. I hope that sounds understandable :)
// elements
let green = document.querySelector('#green');
let red = document.querySelector('#red');
// observer callback
let callback = entries => {
entries.forEach(entry => {
let isInside = entry.intersectionRatio >= 1 ? "fully" : "NOT";
console.log("#" + entry.target.id + " is " + isInside + " inside #container");
});
};
// start observer
let options = {root: document.querySelector('#container'), threshold: 1.0 };
let observer = new IntersectionObserver(callback, options);
observer.observe(green);
observer.observe(red);
// button action
document.querySelector('button').addEventListener('click', () => {
red.style.right = red.style.right == "" ? "0px" : "";
});
#container {
width: 100px;
height: 100px;
background: blue;
position: relative;
}
#green, #red {
width: 50px;
height: 50px;
background: green;
position: absolute;
}
#red {
background: red;
right: -10px;
}
<button>move #red</button>
<br /><br />
<div id="container">
<div id="green"></div>
<div id="red"></div>
</div>
I may not get the question right, what I understand is you want to trigger the IntersectionObserver, so your callback get called. Why don't you call it directly?
document.querySelector('button').addEventListener('click', () => {
red.style.right = red.style.right == "" ? "0px" : "";
callback(red);
});
One way to do it is to use MutationObserver. If a mutation happens (in this case style change) call observe/unobserve on the element that has been changed. This way you don't have to do it for all elements.
Here is an example:
// observer callback
let callback = entries => {
entries.forEach(entry => {
let isInside = entry.intersectionRatio >= 1 ? "fully" : "NOT";
console.log("#" + entry.target.id + " is " + isInside + " inside #container");
});
};
// start observer
let options = {
root: document.querySelector('#container')
};
let observer = new IntersectionObserver(callback, options);
const boxes = document.querySelectorAll('#container > div');
boxes.forEach(box => {
observer.observe(box);
});
// button action
document.querySelector('button').addEventListener('click', () => {
red.style.right = red.style.right == "" ? "0px" : "";
});
// Mutation observer
const targetNode = document.querySelector('#container');
// Options for the observer (which mutations to observe). We only need style changes so we set attributes to true. Also, we are observing the children of container so subtree is true
const config = {
attributes: true,
childList: false,
subtree: true,
attributeFilter: ["style"]
};
// Callback function to execute when mutations are observed
const mutationCallback = (mutationsList, mutationObserver) => {
for (let mutation of mutationsList) {
if (mutation.type === 'attributes') {
console.log('The ' + mutation.attributeName + ' attribute was modified.');
observer.unobserve(mutation.target);
observer.observe(mutation.target);
}
}
};
// Create an observer instance linked to the callback function
const mutationObserver = new MutationObserver(mutationCallback);
// Start observing the target node for configured mutations
mutationObserver.observe(targetNode, config);
#container {
width: 300px;
height: 300px;
background: lightblue;
position: relative;
}
#green,
#red {
width: 100px;
height: 100px;
background: green;
position: absolute;
}
#red {
background: purple;
right: -10px;
}
<button>move #red</button>
<br /><br />
<div id="container">
<div id="green"></div>
<div id="red"></div>
</div>
I have created a set of dots using div tags inside a div tag. My need is when I drag the last dot, the whole set of dots should move and sit where mouse pointer is placed at present. I tried achieving it using addeventlistner for mouse clicks but failed in my attempt.
Can someone point out the intuition in the segment below?
var dots = document.createElement("div");
dots.className = "dots";
document.body.appendChild(dots);
var dotarray = [];
for (index = 0; index < 10; index++) {
dotarray[index] = document.createElement("div");
dotarray[index].className = "dot";
dots.appendChild(dotarray[index]);
}
dotarray[9].addEventListener("mousedown", function(event) {
if (event.which == 1) {
var currentMousePointerPos, latestMousePointerPos;
currentMousePointerPos = event.pageX;
dotarray[9].addEventListener("mouseup", function(event) {
latestMousePointerPos = event.pageX;
if (currentMousePointerPos != latestMousePointerPos) {
dots.style.marginLeft = currentMousePointerPos + latestMousePointerPos;
}
})
}
})
.dot {
width: 8px;
height: 8px;
border-radius: 4px;
background-color: red;
display: inline-block;
margin-left: 5px;
}
.dots {
border: 1px solid black;
width: 135px;
}
The immediate answer to your question is that dots.style.marginLeft needs to be equal to a string, containing the units.
Hence, this would work:
dots.style.marginLeft = ((currentMousePointerPos+latestMousePointerPos) + "px");
However:
Your mouseup listener only listens to the event that the mouse click is released when it's over the element, so it doesn't do much. If you assign the listener to the whole document, the listener's function would be activated no matter where the mouseup event occurres.
currentMousePointerPos + latestMousePointerPos doesn't represent the final position of the mouse.
If we fix these two issues the will code still operate weirdly, because the left side of the dots element is set to the mouse's last position.
Therefore we just need to subtract the element's width from the marginLeft property.
The following code combines everything I've mentioned:
var dots = document.createElement("div");
dots.className = "dots";
document.body.appendChild(dots);
var dotarray = [];
for (index = 0; index < 10; index++) {
dotarray[index] = document.createElement("div");
dotarray[index].className = "dot";
dots.appendChild(dotarray[index]);
}
dotarray[9].addEventListener("mousedown", function(event) {
if (event.which == 1) {
var currentMousePointerPos;
// Notice how the listener is bound to the whole document
document.addEventListener("mouseup", function(event) {
currentMousePointerPos = event.pageX;
dots.style.marginLeft = ((currentMousePointerPos-dots.offsetWidth) + "px");
})
}
})
.dot {
width: 8px;
height: 8px;
border-radius: 4px;
background-color: red;
display: inline-block;
margin-left: 5px;
}
.dots {
border: 1px solid black;
width: 135px;
}
Is this what you are looking for?
You should use the mousemove event instead to detect any dragging
after mousedown.
var dots = document.createElement("div");
dots.className = "dots";
document.body.appendChild(dots);
var dotarray = [];
for (index = 0; index < 10; index++) {
dotarray[index] = document.createElement("div");
dotarray[index].className = "dot";
dots.appendChild(dotarray[index]);
}
dotarray[9].addEventListener("mousedown", function(event) {
if (event.which == 1) {
window.addEventListener('mousemove', move, true);
/*var currentMousePointerPos, latestMousePointerPos;
currentMousePointerPos = event.pageX;
dotarray[9].addEventListener("mouseup", function(event) {
latestMousePointerPos = event.pageX;
if (currentMousePointerPos != latestMousePointerPos) {
dots.style.marginLeft = currentMousePointerPos + latestMousePointerPos;
}
})*/
}
});
window.addEventListener("mouseup", function(event) {
window.removeEventListener('mousemove', move, true);
});
function move(e){
var div = document.getElementsByClassName('dots')[0];
div.style.position = 'absolute';
div.style.top = -8+e.clientY + 'px';
div.style.left = -135+8+e.clientX + 'px';
}
.dot {
width: 8px;
height: 8px;
border-radius: 4px;
background-color: red;
display: inline-block;
margin-left: 5px;
}
.dots {
border: 1px solid black;
width: 135px;
}
I have written an javascript that generates the dynamic elements based on the jSON data supplied to it.
$(function () {
var list = JSON.parse(#ViewBag.NomineeList);
var counter = 1;
var tr;
$(list).each((function () {
if (counter % 2 != 0) {
tr = CreateElems('tr', null, null);
}
var td = CreateElems('td', null, null);
var div = CreateElems('div', 'dvBorder', null);
div.attr('empID', this.EmpId);
div.attr('nomineeID', this.Id);
RegisterEvents(div);
div.append('<img alt="user" src=' + this.UserImagePath + ' style="padding: 5px;" />');
div.append(CreateElems('span', 'EmpolyeeName', this.FirstName));
div.append(CreateElems('span', 'EmployeeEmail', this.Email));
td.append(div);
tr.append(td);
if (counter % 2 == 0) {
$('#tblEmployee').append(tr);
tr = "";
}
counter++;
}));
});
function CreateElems(type,cssClass,value)
{
var elem = $(document.createElement(type));
if(value != null)
elem.text(value);
if(cssClass!= null)
elem.addClass(cssClass);
return elem;
}
There are three different events that i have registered for the dynamic elements that are created.
function RegisterEvents(crntDiv) {
var url;
$(crntDiv).click(function () {
url = "/home/SaveVote?nomineeId=" + $(crntDiv).attr('nomineeID');
AjaxCall(url, false, crntDiv);
});
$(crntDiv).mouseover(function () {
RemoveToolTip();
url = "/home/GetDescription?nomineeId=" + $(crntDiv).attr('nomineeID');
AjaxCall(url, true, crntDiv);
});
$(crntDiv).mouseout(function () {
$(crntDiv).children('div.RollOverTip').remove();
});
}
when you mouse over the tool tip comes up . On that event I am checking if any previous tool tip is present in dom it should be removed.
function RemoveToolTip() {
$('#tblEmployee').find('div.RollOverTip').remove();
}
But still there are times when there are more than two three tool tips are present on the browser. Also can this be optimized a bit.
Html
<table border="0" cellpadding="5" cellspacing="0" id="tblEmployee">
</table>
css Classes.
.dvBorder
{
background-image: url(/Images/screen2-button.png);
background-repeat: no-repeat;
height: 125px;
width: 400px;
cursor:pointer;
position: relative;
}
.RollOverTip
{
background-image: url("/Images/screen2-rollover-tooltip.png");
background-repeat: no-repeat;
color: #000000;
font-family: Calibri Regular;
font-size: 18pt;
height: 199px;
line-height: 20pt;
margin-left: 385px;
position: absolute;
width: 474px;
z-index: 90000;
padding:34px;
}
What are the optimization possible in the script,also any suggestions to remove the flickering?
Try using mouseenter and mouseleave to stop the flickering
$(crntDiv).mouseenter(function () {
RemoveToolTip();
url = "/home/GetDescription?nomineeId=" + $(crntDiv).attr('nomineeID');
AjaxCall(url, true, crntDiv);
});
$(crntDiv).mouseleave(function () {
$(crntDiv).children('div.RollOverTip').remove();
});
Try to use mouseenter and mouseleave it might fix the problem instead of mouseover and mouseout
EDIT
Try this :
change
$(crntDiv).mouseover(function () {...}
to
$(document).on('mouseenter',$(crntDiv,'#tblEmployee div.RollOverTip'), function () {...}