Find the Word the Mouse is Pointing to on a Webpage with JavaScript

Published: May 31, 2024

Find the Word the Mouse is Pointing to on a Webpage with JavaScript

Background

I am using a Chrome extension called RikaiKun, which shows the meaning of a Japanese word by just hovering over that word.

Rikai Kun

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.

Method: Mode Human

Firstly, let's write code to find which element is currently hovered and get its text:

Hovered word: -

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.

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.

Method: Mode Animal

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:

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.

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.

Doesn't Work: 1. Programatically fire double click event

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.

Doesn't Work: 2. Try to calculate which word is being hovered on

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.

Works: 3. Divide text into spans

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.

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.

Result

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?

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.

Conclusion

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

Create A Simple and Dynamic Tooltip With Svelte and JavaScript

2024/06/19

Create A Simple and Dynamic Tooltip With Svelte and JavaScript

JavaScriptSvelteSimpleDynamicTooltipFront-end
Create an Interactive Map of Tokyo with JavaScript

2024/06/17

Create an Interactive Map of Tokyo with JavaScript

SvelteSVGJavaScriptTailwindInteractive MapTokyoJapanTokyo Metropolitan Area23 Wards
How to Easily Fix Japanese Character Issue in Matplotlib

2024/06/14

How to Easily Fix Japanese Character Issue in Matplotlib

MatplotlibGraphChartPythonJapanese charactersIssueBug
Book Review | Talking to Strangers: What We Should Know about the People We Don't Know by Malcolm Gladwell

2024/06/13

Book Review | Talking to Strangers: What We Should Know about the People We Don't Know by Malcolm Gladwell

Book ReviewTalking to StrangersWhat We Should Know about the People We Don't KnowMalcolm Gladwell
Most Commonly Used 3,000 Kanjis in Japanese

2024/06/07

Most Commonly Used 3,000 Kanjis in Japanese

Most CommonKanji3000ListUsage FrequencyJapaneseJLPTLanguageStudyingWordsKanji ImportanceWord Prevalence
Replace With Regex Using VSCode

2024/06/07

Replace With Regex Using VSCode

VSCodeRegexFindReplaceConditional Replace
Do Not Use Readable Store in Svelte

2024/06/06

Do Not Use Readable Store in Svelte

SvelteReadableWritableState ManagementStoreSpeedMemoryFile Size
Increase Website Load Speed by Compressing Data with Gzip and Pako

2024/06/05

Increase Website Load Speed by Compressing Data with Gzip and Pako

GzipCompressionPakoWebsite Load SpeedSvelteKit
Create an Interactive Map with Svelte using SVG

2024/05/29

Create an Interactive Map with Svelte using SVG

SvelteSVGInteractive MapFront-end
Book Review | Originals: How Non-Conformists Move the World by Adam Grant & Sheryl Sandberg

2024/05/28

Book Review | Originals: How Non-Conformists Move the World by Adam Grant & Sheryl Sandberg

Book ReviewOriginalsAdam Grant & Sheryl SandbergHow Non-Conformists Move the World
How to Algorithmically Solve Sudoku Using Javascript

2024/05/27

How to Algorithmically Solve Sudoku Using Javascript

Solve SudokuAlgorithmJavaScriptProgramming
How I Increased Traffic to my Website by 10x in a Month

2024/05/26

How I Increased Traffic to my Website by 10x in a Month

Increase Website TrafficClicksImpressionsGoogle Search Console
Life is Like Cycling

2024/05/24

Life is Like Cycling

CyclingLifePhilosophySuccess
Generate a Complete Sudoku Grid with Backtracking Algorithm in JavaScript

2024/05/19

Generate a Complete Sudoku Grid with Backtracking Algorithm in JavaScript

SudokuComplete GridBacktracking AlgorithmJavaScript
Why Tailwind is Amazing and How It Makes Web Dev a Breeze

2024/05/16

Why Tailwind is Amazing and How It Makes Web Dev a Breeze

TailwindAmazingFront-endWeb Development
Generate Sitemap Automatically with Git Hooks Using Python

2024/05/15

Generate Sitemap Automatically with Git Hooks Using Python

Git HooksPythonSitemapSvelteKit
Book Review | Range: Why Generalists Triumph in a Specialized World by David Epstein

2024/05/14

Book Review | Range: Why Generalists Triumph in a Specialized World by David Epstein

Book ReviewRangeDavid EpsteinWhy Generalists Triumph in a Specialized World
What is Svelte and SvelteKit?

2024/05/13

What is Svelte and SvelteKit?

SvelteSvelteKitFront-endVite
Internationalization with SvelteKit (Multiple Language Support)

2024/05/12

Internationalization with SvelteKit (Multiple Language Support)

InternationalizationI18NSvelteKitLanguage Support
Reduce Svelte Deploy Time With Caching

2024/05/11

Reduce Svelte Deploy Time With Caching

SvelteEnhanced ImageCachingDeploy Time
Lazy Load Content With Svelte and Intersection Oberver

2024/05/10

Lazy Load Content With Svelte and Intersection Oberver

Lazy LoadingWebsite Speed OptimizationSvelteIntersection Observer
Find the Optimal Stock Portfolio with a Genetic Algorithm

2024/05/10

Find the Optimal Stock Portfolio with a Genetic Algorithm

Stock marketPortfolio OptimizationGenetic AlgorithmPython
Convert ShapeFile To SVG With Python

2024/05/09

Convert ShapeFile To SVG With Python

ShapeFileSVGPythonGeoJSON
Reactivity In Svelte: Variables, Binding, and Key Function

2024/05/08

Reactivity In Svelte: Variables, Binding, and Key Function

SvelteReactivityBindingKey Function
Book Review | The Art Of War by Sun Tzu

2024/05/07

Book Review | The Art Of War by Sun Tzu

Book ReviewThe Art Of WarSun TzuThomas Cleary
Specialists Are Dead. Long Live Generalists!

2024/05/06

Specialists Are Dead. Long Live Generalists!

SpecialistGeneralistParadigm ShiftSoftware Engineering
Analyze Voter Behavior in Turkish Elections with Python

2024/05/03

Analyze Voter Behavior in Turkish Elections with Python

TurkeyAge Analysis2018 ElectionsVoter Behavior
Create Turkish Voter Profile Database With Web Scraping

2024/05/01

Create Turkish Voter Profile Database With Web Scraping

PythonSeleniumWeb ScrapingTurkish Elections
Make Infinite Scroll With Svelte and Tailwind

2024/04/30

Make Infinite Scroll With Svelte and Tailwind

SvelteTailwindInfinite ScrollFront-end
How I Reached Japanese Proficiency In Under A Year

2024/04/29

How I Reached Japanese Proficiency In Under A Year

JapaneseProficiencyJLPTBusiness
Use-ready Website Template With Svelte and Tailwind

2024/04/25

Use-ready Website Template With Svelte and Tailwind

Website TemplateFront-endSvelteTailwind
Lazy Engineers Make Lousy Products

2024/01/29

Lazy Engineers Make Lousy Products

Lazy engineerLousy productStarbucksSBI
On Greatness

2024/01/28

On Greatness

GreatnessMeaning of lifeSatisfactory lifePurpose
Converting PDF to PNG on a MacBook

2024/01/28

Converting PDF to PNG on a MacBook

PDFPNGMacBookAutomator
Recapping 2023: Compilation of 24 books read

2023/12/31

Recapping 2023: Compilation of 24 books read

BooksReading2023Reflections
Create a Photo Collage with Python PIL

2023/12/30

Create a Photo Collage with Python PIL

PythonPILImage ProcessingCollage
Detect Device & Browser of Visitors to Your Website

2024/01/09

Detect Device & Browser of Visitors to Your Website

JavascriptDevice DetectionBrowser DetectionWebsite Analytics
Anatomy of a ChatGPT Response

2024/01/19

Anatomy of a ChatGPT Response

ChatGPTLarge Language ModelMachine LearningGenerative AI