Skip to content
This repository was archived by the owner on Apr 9, 2025. It is now read-only.

Commit a3e993e

Browse files
bast0006humitos
andauthored
Add configuration to improve large page experience (#227)
* Change tooltipster application strategy to support large pages Use dynamic application of the tooltipsterification process to create it, then open it, on click or hover rather than processing potentially thousands of elements during the page load event. The tooltipster docs suggest that this is a less performant strategy (I expect very small hiccups on hover), but for large pages it is much more preferable than a second-long load delay. This will be refactored later into an option * Revert "Change tooltipster application strategy to support large pages" This reverts commit 8c36803. * Add new configuration `hoverxref_tooltip_lazy` to support large pages efficiently * Correct docs to appropriately match function and add clarity as suggested * Rework hoverxref_tooltip_lazy if statements to be cleaner * Add tests for hoverxref_tooltip_lazy option Co-authored-by: Manuel Kaufmann <[email protected]>
1 parent 926e35c commit a3e993e

File tree

4 files changed

+94
-8
lines changed

4 files changed

+94
-8
lines changed

docs/configuration.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,16 @@ These settings have effect only in tooltips.
177177
Type: string
178178

179179

180+
.. confval:: hoverxref_tooltip_lazy
181+
Description: Whether to lazily generate tooltips (insert the HTML for the tooltip on hover, rather than on page load).
182+
This is known to be slower, but prevents the browser from stalling on load for very big doc pages.
183+
We recommend you keeping it as `False` unless you are experiencing page load issues.
184+
185+
Default ``False``
186+
187+
Type: bool
188+
189+
180190
.. warning::
181191

182192
The following settings are passed directly to Tooltipster_.

hoverxref/_static/js/hoverxref.js_t

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -91,14 +91,8 @@ function getEmbedURL(url) {
9191
return url
9292
}
9393

94-
95-
$(document).ready(function() {
96-
// Remove ``title=`` attribute for intersphinx nodes that have hoverxref enabled.
97-
// It doesn't make sense the browser shows the default tooltip (browser's built-in)
98-
// and immediately after that our tooltip was shown.
99-
$('.{{ hoverxref_css_class_prefix }}hoverxref.external').each(function () { $(this).removeAttr('title') });
100-
101-
$('.{{ hoverxref_css_class_prefix }}hoverxref.{{ hoverxref_css_class_prefix }}tooltip').tooltipster({
94+
function addTooltip(target) {
95+
return target.tooltipster({
10296
theme: {{ hoverxref_tooltip_theme }},
10397
interactive: {{ 'true' if hoverxref_tooltip_interactive else 'false' }},
10498
maxWidth: {{ hoverxref_tooltip_maxwidth }},
@@ -144,6 +138,33 @@ $(document).ready(function() {
144138
);
145139
}
146140
})
141+
}
142+
143+
144+
$(document).ready(function() {
145+
// Remove ``title=`` attribute for intersphinx nodes that have hoverxref enabled.
146+
// It doesn't make sense the browser shows the default tooltip (browser's built-in)
147+
// and immediately after that our tooltip was shown.
148+
149+
// Support lazy-loading here by switching between on-page-load (calling .tooltipster directly)
150+
// or delaying it until after a mouseenter or click event.
151+
// On large pages (the use case for lazy-loaded tooltipster) this moves dom manipulation to
152+
// on-interaction, which is known to be less performant on small pages (as per tooltipster docs),
153+
// but on massive docs pages fixes an otherwise severe page load stall when tooltipster
154+
// manipulates the html for every single tooltip at once.
155+
{% if hoverxref_tooltip_lazy %}
156+
$('.{{ hoverxref_css_class_prefix }}hoverxref.{{ hoverxref_css_class_prefix }}tooltip').one('mouseenter click touchstart tap', function(event) {
157+
$(this).removeAttr('title');
158+
{# If we're lazy-tipstering, we've used up the click event, so open the modal too with tooltipster('open') #}
159+
addTooltip($(this)).tooltipster('open');
160+
});
161+
{% else %}
162+
$('.{{ hoverxref_css_class_prefix }}hoverxref.external').each(function () { $(this).removeAttr('title') });
163+
addTooltip(
164+
$('.{{ hoverxref_css_class_prefix }}hoverxref.{{ hoverxref_css_class_prefix }}tooltip')
165+
)
166+
{% endif %}
167+
147168

148169
var modalHtml = `
149170
<div class="modal micromodal-slide {{ hoverxref_modal_class }}" id="micromodal" aria-hidden="true">

hoverxref/extension.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@ def setup(app):
351351
app.add_config_value('hoverxref_intersphinx_types', {}, 'env')
352352
app.add_config_value('hoverxref_api_host', 'https://readthedocs.org', 'env')
353353
app.add_config_value('hoverxref_sphinx_version', sphinx.__version__, 'env')
354+
app.add_config_value('hoverxref_tooltip_lazy', False, 'env')
354355

355356
# Tooltipster settings
356357
# Deprecated in favor of ``hoverxref_api_host``

tests/test_jsconf.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import pytest
2+
3+
from .utils import srcdir
4+
5+
6+
@pytest.mark.sphinx(
7+
srcdir=srcdir,
8+
confoverrides={
9+
'hoverxref_tooltip_lazy': True,
10+
},
11+
)
12+
def test_lazy_tooltips(app, status, warning):
13+
app.build()
14+
path = app.outdir / '_static/js/hoverxref.js'
15+
assert path.exists() is True
16+
content = open(path).read()
17+
18+
chunks = [
19+
".one('mouseenter click touchstart tap', function(event) {",
20+
".tooltipster('open');",
21+
]
22+
23+
for chunk in chunks:
24+
assert chunk in content
25+
26+
ignored_chunks = [
27+
".each(function () { $(this).removeAttr('title') });",
28+
]
29+
for chunk in ignored_chunks:
30+
assert chunk not in content
31+
32+
33+
@pytest.mark.sphinx(
34+
srcdir=srcdir,
35+
)
36+
def test_lazy_tooltips_notlazy(app, status, warning):
37+
app.build()
38+
path = app.outdir / '_static/js/hoverxref.js'
39+
assert path.exists() is True
40+
content = open(path).read()
41+
42+
chunks = [
43+
".each(function () { $(this).removeAttr('title') });",
44+
]
45+
46+
for chunk in chunks:
47+
assert chunk in content
48+
49+
ignored_chunks = [
50+
".one('mouseenter click touchstart tap', function(event) {",
51+
".tooltipster('open');",
52+
]
53+
for chunk in ignored_chunks:
54+
assert chunk not in content

0 commit comments

Comments
 (0)