Description
AnGo Scroll Analytics records how far visitors scroll through your posts and pages and shows the result on a “Readability” screen in wp-admin. It answers the question a pageview count cannot: did anyone actually read past the first screen?
The tracking script is loaded only on single posts and pages (is_singular for the post and page types) and measures how far the page was scrolled during a visit. That is depth through the scrollable height of the document, not the share of the article visible on screen: a page that fits on one screen records 100%.
A visit is registered as soon as the page loads — a visitor who scrolls nowhere still gets a row, with 0%. After that, data is sent to the server only when reading progress actually advances (checked every 5 seconds), and always when the visitor leaves the page or switches tabs (via navigator.sendBeacon, or fetch with keepalive where it is unavailable). A failed send is retried at the next check.
One visit is one row in the table, updated as the reader gets further down. A new tab or a new session is a new visit.
What the dashboard shows
- The period, chosen by hand (the last 30 days by default), with an “Apply” button and grouping by day or by month.
- A summary for the selected period: number of visits, average scroll %, median.
- Percentiles 50/75/90/100, phrased as “X% of visitors scrolled at least to Y% of the article”.
- A trend chart: average scroll as a line, visits as bars.
- A table per item: title, type (post/page), visits, average scroll, median, 90th percentile, sorted by visit count.
Performance at scale
The plugin computes the statistics on the fly with database queries. At very large volumes (hundreds of thousands of visits) caching (transients) or cron-based aggregation is worth adding. A typical blog does not need it.
Rate limiting and the database
Without a persistent object cache, each beacon writes the throttle counter to the options table, so the limit costs two queries per beacon. That is cheap at the scale this plugin targets — it is a spam brake, not a firewall.
Installation
- In wp-admin go to Plugins Add New Upload Plugin.
- Choose the downloaded archive (ango-scroll-analytics-1.2.1.zip), click “Install Now”, then “Activate”.
- On activation the plugin creates its table in the database, {prefix}_ango_visits (wp_ango_visits with the default prefix). If the schema changes in a later version, the table is brought up to date the next time wp-admin is opened (dbDelta).
- A “Readability” entry appears in the left-hand menu — the dashboard lives there.
FAQ
-
Do visits with no scrolling really count?
-
Yes. Every arrival is a row, including the ones with 0% scroll: the percentiles describe all arrivals, not only those who read to the end. That is a deliberate decision, not lost data.
-
Is the tracking endpoint public?
-
Yes — it has to be, because visitors are not logged in. POST /wp-json/ango/v1/depth is limited to 60 requests per minute per IP; the limit is changed through the ango_rate_limit filter, and a value below 1 disables the check. The IP itself is not stored, only a hash of it, for the duration of the window. Behind a proxy (Cloudflare and similar) REMOTE_ADDR is the proxy address, so the limit is shared by all visitors — raise it in that setup.
-
Why do the numbers differ from my analytics tool?
-
This plugin measures depth through the scrollable height of the page, not the share of the article that was on screen: a short page that fits on one screen records 100%. And it counts visits, not unique readers — a new tab or a new session is a new visit.
-
What timezone are the numbers in?
-
Timestamps in the table (created_at) are stored in UTC, while the dashboard computes day boundaries in the site timezone. Keep that in mind when writing manual SQL against {prefix}_ango_visits.
-
Can I track custom post types?
-
The tracking script is loaded only on single posts and pages (is_singular( [ ‘post’, ‘page’ ] )). To track custom post types as well, add them to that list in ango-scroll-analytics.php, in ango_enqueue_tracker().
-
Does the admin screen load anything from outside my site?
-
No. The chart is drawn by Chart.js 4.5.1, bundled with the plugin (assets/vendor/chart.umd.min.js, MIT license), so wp-admin makes no external request and the chart works offline and behind a filtering proxy.
-
Why does the bundled Chart.js ship minified?
-
Chart.js 4.5.1 (MIT) ships here as the minified UMD build that upstream publishes — assets/vendor/chart.umd.min.js, licence banner intact. Upstream has no unminified UMD to use instead: dist/chart.umd.js and dist/chart.umd.min.js in its package are both minified. The readable code is published, just in another build: the unminified ESM bundle is dist/chart.js (https://unpkg.com/chart.js@4.5.1/dist/chart.js), and the sources it is built from are at https://github.com/chartjs/Chart.js/tree/v4.5.1/src.
-
Why are the file names so plain?
-
They are deliberately neutral: the tracking script is assets/js/scroll-depth.js and the public endpoint is /wp-json/ango/v1/depth, with no tracker/track in either. Those words are a common target for filter lists, and a blocked script or beacon loses data silently. Neither EasyList/EasyPrivacy nor the uBlock Origin and AdGuard filters matched these URLs (checked 2026-09-16 under the plugin’s previous name; the endpoint paths did not change in the rename), but the lists update daily — so do not rename them back “for clarity”.
-
How do I update the Russian translation?
-
English is the source language; Russian is a translation (languages/ango-scroll-analytics-ru_RU.po). For the WordPress.org-hosted plugin the catalogue is served by translate.wordpress.org and no translation files ship in the package — the .po lives in the development repository for contributors. A site that installs the plugin from the WordPress.org directory receives community translations automatically; WordPress reads the compiled .mo, so after editing the .po you must rebuild it:
msgfmt -o languages/ango-scroll-analytics-ru_RU.mo languages/ango-scroll-analytics-ru_RU.po
If gettext is not installed, a minimal Python MO writer does the same job. The test suite checks that the .po and the .mo stay in agreement: python3 tests/run.py
-
What happens to my data when I uninstall the plugin?
-
Deleting the plugin removes everything it stored: the {prefix}_ango_visits table, the ango_db_version option and the throttle transients. Deactivating does not — the data survives a deactivate and reactivate cycle, so an upgrade or a temporary switch-off costs nothing. Clicking Delete on the Plugins screen shows a confirmation naming the statistics that will be lost. If you need the numbers after the plugin is gone, export them before deleting, or hold the cleanup back from a must-use plugin with add_filter( ‘ango_remove_data_on_uninstall’, ‘__return_false’ ).
Reviews
There are no reviews for this plugin.
Contributors & Developers
“AnGo Scroll Analytics” is open source software. The following people have contributed to this plugin.
ContributorsTranslate “AnGo Scroll Analytics” into your language.
Interested in development?
Browse the code, check out the SVN repository, or subscribe to the development log by RSS.