Published: May 31, 2024
I am using a Chrome extension called RikaiKun, which shows the meaning of a Japanese word by just hovering over that word.
Note that you don't need to select the word for it to guess the word and find its meaning. You just need to hover over the text.
So in this blogpost, let's try to implement the same thing.
Firstly, let's write code to find which element is currently hovered and get its text:
Hovered word: -
Below is the code to achieve this:
<script>
import { onMount } from 'svelte';
let hoveredWord;
let text = `All states and governments that ever ruled over men have been either republics or monarchies. Monarchies may be hereditary, if the ruler’s family has governed for generations, or new. New monarchies can either be entirely new, as when Francesco Sforza captured Milan, or they could be territories a ruler has added to his existing hereditary state by conquest, as when the King of Spain took Naples. An additional territory won by conquest will be accustomed either to living under a monarch or to the freedom of self government and may be conquered by the new ruler’s own army or that of a third party, by luck or deservedly.`;
onMount(async () => {
document.addEventListener('mouseover', function (e) {
document.querySelectorAll('.textContainer span').forEach((node) => {
node.style.color = '';
});
let target = e.target.closest('.textContainer span');
if (target) {
target.style.color = 'red';
hoveredWord =
target.innerText.replace(',', '').replace('.', '') ||
target.textContent.replace(',', '').replace('.', '');
}
});
});
</script>
<p class="text-center font-bold">Hovered word: {hoveredWord ? hoveredWord : '-'}</p>
<div class="textContainer px-4 text-sm">
{#each text.split(' ') as word}
<span class="italic">{`${word} `}</span>
{/each}
</div>
By looking at above code, you'll see that we are currently playing in easy mode. This is because each word in our text wrapped in span element so it is very easy to see which element is highlighted and get the only word that element has.
Nevertheless, it is good practice to see how we can identify where the mouse is through mouseover event.
Now let's make things a little more difficult and fun.
Now, let's have the same text in bulk, as in it is not separated into words. We now need to find which word the mouse is hovering over among this large text:
Let's see how the function we developed will work:
You can see that when you hover over the text, the whole text is highlighted which is not what we want. We only want to highlight the word where the mouse is hovering. So how can we find the exact word that is being hovered on? I hypothesized there must be 3 way to do this.
You know that when you double click on a text in PC, it automatically highlights only the word where the mouse is hovering over. I thought, if we can somehow automatically fire this, the word will be selected.
After the word is selected, we can get the selected word using below code:
// When mouse is hovered on a word, trigger double click to select that word
document.addEventListener('mouseover', function (e) {
var event = new MouseEvent('dblclick', {
view: window,
bubbles: true,
cancelable: true
});
// Programatically fire it for the element we are interested in
document.getElementById('textContainer').dispatchEvent(event);
});
// Get the selection when souble click fires
document.addEventListener('dblclick', function (e) {
let target = e.target.closest('#textContainer');
// Check if it is firing for the textContainer
if (target) {
let selectedWord =
(document.selection && document.selection.createRange().text) ||
(window.getSelection && window.getSelection().toString());
// Do something (E.g. Show meaning of that word)
}
});
Bad news! Unfortunately this doesn't work as the word does not get selected automatically as we fire double click event.
When a div element is hovered, we can get its width and height, and get its text content & line height. Combine this with the mouse's location with respect to the div element, we can try to guess where the mouse is located:
document.addEventListener('mouseover', function (e) {
let target = e.target.closest('.textContainer span');
// Get width and height of container as this will be different depending on viewport
if (target) {
let targetRect = target.getBoundingClientRect();
let height = targetRect.height;
let width = targetRect.width;
// ...
}
});
I did not complete the code as this approach is actually super complex and even if we do end up writing a good algorithm, it is bound to be wrong when it comes to edge cases. So let's just skip this option.
This approach is actually what we have used at the beginning of this post. What we are going to do is, we will get our target element and divide its text content into separate span elements.
This will enable us regenerate each word as a separate span element and have control over its events.
To make things a little more difficult than what we discussed originally, let's assume that our target element has various sub-elements like div, p, anchor, span, and so on. Let's see how we can convert a parent element with various subelements into separate span elements while keeping the classes, ids, tags of the original elements intact - only working on text.
New monarchies can either be entirely new, as when Francesco Sforza captured Milan, or they could be territories a ruler has added to his existing hereditary state by conquest, as when the King of Spain took Naples.
Below is the code we'll be using to achieve our aim:
<script>
// Get the container we want to modify
let container = document.getElementById('textContainer');
// Check if there is any text that is directly under the container
let firstBit = container.innerHTML[0] !== '<' ? container.innerHTML.split('<')[0] : '';
let lastBit =
container.innerHTML.slice(-1) !== '>' ? container.innerHTML.split('>').slice(-1) : '';
// Add those text to a span element
if (firstBit) {
container.innerHTML = `<span>${firstBit}</span>` + container.innerHTML.replace(firstBit, '');
}
if (lastBit) {
container.innerHTML = container.innerHTML.replace(lastBit, '') + `<span>${lastBit}</span>`;
}
// Get children of the container
let children = getAllDescendants(container);
// Get texts of children that do not have any further children
let texts = children.filter((c) => c.childNodes.length === 0).map((c) => c.textContent);
// For each text block, split it into words and convert each word to a span element
texts.forEach((t) => {
container.innerHTML = container.innerHTML
.replace(
`>${t}<`,
`>${t
.split(' ')
.map((t) => `<span class="hover">${t}</span> `)
.join('')}<`
)
.replace(
`>"${t}"<`,
`>${t
.split(' ')
.map((t) => `<span class="hover">${t}</span> `)
.join('')}<`
);
});
</script>
// This is the HTML container we will be manipulating
// With manipulation, we mean we are going to divide its text into words
// And enclose each word with span element
<div class="px-4 text-sm" id="textContainer">
All states and governments that ever ruled over men have been either republics or monarchies. Monarchies
may be hereditary, if the
<a class="font-bold text-sky-500" href="javascript:void(0)">ruler’s family</a> has governed for
generations, or new.
<p>
New monarchies can either be entirely new, as when Francesco Sforza captured Milan, or they
could be territories a ruler has added to his existing hereditary state by conquest, as when
the King of Spain took Naples.
</p>
<div><div><span class="font-bold">An additional territory</span></div></div>
won by conquest will be accustomed either to living
<span class="italic">under a monarch</span>
or to the freedom of self government and may be conquered by the new ruler’s own army or that of
a third party, <span class="text-lg">by luck</span> or deservedly.
</div>
</div>
// Add hover class for highlighting each word
<style>
:global(.hover:hover) {
background: yellow;
} </style>
When applied, hover over below text to see whether each word is highlighted as you hover over them. It works right?
New monarchies can either be entirely new, as when Francesco Sforza captured Milan, or they could be territories a ruler has added to his existing hereditary state by conquest, as when the King of Spain took Naples.
In this blog post, we have developed a way to highlight each word as the user hovers over them. The code developed can be used for other purposes such as showing the word's meaning or opening a modal giving information about that word.
I haven't tested whether the code works in edge cases, but if you do find any issue, let me know.
Until further notice, happy coding!
Leave comment
Comments
Check out other blog posts
2024/06/19
Create A Simple and Dynamic Tooltip With Svelte and JavaScript
2024/06/17
Create an Interactive Map of Tokyo with JavaScript
2024/06/14
How to Easily Fix Japanese Character Issue in Matplotlib
2024/06/13
Book Review | Talking to Strangers: What We Should Know about the People We Don't Know by Malcolm Gladwell
2024/06/07
Most Commonly Used 3,000 Kanjis in Japanese
2024/06/07
Replace With Regex Using VSCode
2024/06/06
Do Not Use Readable Store in Svelte
2024/06/05
Increase Website Load Speed by Compressing Data with Gzip and Pako
2024/05/29
Create an Interactive Map with Svelte using SVG
2024/05/28
Book Review | Originals: How Non-Conformists Move the World by Adam Grant & Sheryl Sandberg
2024/05/27
How to Algorithmically Solve Sudoku Using Javascript
2024/05/26
How I Increased Traffic to my Website by 10x in a Month
2024/05/24
Life is Like Cycling
2024/05/19
Generate a Complete Sudoku Grid with Backtracking Algorithm in JavaScript
2024/05/16
Why Tailwind is Amazing and How It Makes Web Dev a Breeze
2024/05/15
Generate Sitemap Automatically with Git Hooks Using Python
2024/05/14
Book Review | Range: Why Generalists Triumph in a Specialized World by David Epstein
2024/05/13
What is Svelte and SvelteKit?
2024/05/12
Internationalization with SvelteKit (Multiple Language Support)
2024/05/11
Reduce Svelte Deploy Time With Caching
2024/05/10
Lazy Load Content With Svelte and Intersection Oberver
2024/05/10
Find the Optimal Stock Portfolio with a Genetic Algorithm
2024/05/09
Convert ShapeFile To SVG With Python
2024/05/08
Reactivity In Svelte: Variables, Binding, and Key Function
2024/05/07
Book Review | The Art Of War by Sun Tzu
2024/05/06
Specialists Are Dead. Long Live Generalists!
2024/05/03
Analyze Voter Behavior in Turkish Elections with Python
2024/05/01
Create Turkish Voter Profile Database With Web Scraping
2024/04/30
Make Infinite Scroll With Svelte and Tailwind
2024/04/29
How I Reached Japanese Proficiency In Under A Year
2024/04/25
Use-ready Website Template With Svelte and Tailwind
2024/01/29
Lazy Engineers Make Lousy Products
2024/01/28
On Greatness
2024/01/28
Converting PDF to PNG on a MacBook
2023/12/31
Recapping 2023: Compilation of 24 books read
2023/12/30
Create a Photo Collage with Python PIL
2024/01/09
Detect Device & Browser of Visitors to Your Website
2024/01/19
Anatomy of a ChatGPT Response