\n \n \n\n\n
\n
\n \n
\n
\n \n
\n
\n\n \n\n\n```\n\nBoom—scroll one, both move. **Alpine.js computed values**? Add `scrollPos: 0` and `$watch('scrollPos', val => { this.editor2.scrollTop = val; })`, but events suffice for most.\n\nTested this? Line up matching text; cursors stay synced. For diffs, highlight changes—scroll locks 'em tight.\n\n---\n\n## Toolbar Visibility and Fixes {#toolbar-fixes}\n\nToolbars hiding? Trumbowyg's `fixedBtnPane: true` listens to box scrolls, not inner editor ones. Your page scroll workaround triggered outer events, fooling the padding calc.\n\nFix: Ensure listeners are strictly on `.trumbowyg-editor`. If toolbars still dip, force the padding refresh:\n\n```javascript\nconst refreshToolbar = (box) => {\n const toolbar = box.querySelector('.trumbowyg-button-pane');\n box.style.paddingTop = toolbar.offsetHeight + 'px';\n};\n```\n\nCall after sync. Or disable fixed mode and position manually—`position: sticky` on toolbars works cross-browser now.\n\nEdge cases? Mobile touch-scrolls lag? Throttle with lodash or native:\n\n```javascript\nlet ticking = false;\nfunction throttledSync(source, target) {\n if (!ticking) {\n requestAnimationFrame(() => {\n target.scrollTop = source.scrollTop;\n ticking = false;\n });\n ticking = true;\n }\n}\n```\n\n**Sync scroll** feels native. Resize windows? Add `@resize.window` in Alpine to rebind refs.\n\n---\n\n## Advanced Library Option {#syncscroll-lib}\n\nWant zero boilerplate? Drop in [syncscroll library](https://github.com/asvd/syncscroll)—946 bytes, vanilla JS. Just add `class=\"syncscroll\" name=\"mygroup\"` to both `.trumbowyg-editor` divs post-init:\n\n```javascript\nsyncscroll({groups: [{name: 'mygroup'}]});\n```\n\nIt handles multi-element sync out-of-box, even horizontal if needed. Like [diff2html's side-by-side](https://github.com/rtfpessoa/diff2html), but pure scroll. Integrate with **Alpine.js** via `x-init`.\n\nOverkill for two editors? Maybe. But scales if you add more panes. No perf hit—micro indeed.\n\n---\n\n## Sources {#sources}\n\n1. [Stack Overflow: Sync scroll between 2 textarea editors](https://stackoverflow.com/questions/79860126/sync-scroll-between-2-texarea-editors) \n2. [GitHub: asvd/syncscroll](https://github.com/asvd/syncscroll) \n3. [.NET Curry: Synchronize Scrolling of Two Multiline TextBoxes](https://www.dotnetcurry.com/ShowArticle.aspx?ID=422) \n4. [Trumbowyg Documentation](https://alex-d.github.io/Trumbowyg/documentation/) \n5. [Alpine.js: on directive](https://alpinejs.dev/directives/on) \n6. [GitHub: rtfpessoa/diff2html](https://github.com/rtfpessoa/diff2html)\n\n---\n\n## Conclusion {#conclusion}\n\nSyncing **Trumbowyg** editors with **Alpine.js** boils down to targeting `.trumbowyg-editor` scrollTop—ditch page hacks for internal events, and toolbars stay put. Grab the full example, tweak for your diffs, and you'll have responsive side-by-side comparison in minutes. Vanilla works too; scale with syncscroll if needed. Questions on **alpine js computed values** for positions? It's reactive magic waiting."},{"name":"Sync vertical scrolling between two Trumbowyg editors with Alpine.js","step":[{"name":"Initialize Trumbowyg on both textareas with fixedBtnPane enabled","@type":"HowToStep","@id":"https://neuroanswers.net/c/web/q/sync-trumbowyg-editors-alpine-js","position":1},{"name":"After initialization, select each .trumbowyg-editor contenteditable div from the DOM or via $refs","@type":"HowToStep","@id":"https://neuroanswers.net/c/web/q/sync-trumbowyg-editors-alpine-js","position":2},{"name":"Attach scroll listeners to both .trumbowyg-editor elements to read scrollTop","@type":"HowToStep","@id":"https://neuroanswers.net/c/web/q/sync-trumbowyg-editors-alpine-js","position":3},{"name":"Mirror the source editor's scrollTop to the target inside requestAnimationFrame and use a syncing flag to avoid infinite loops","@type":"HowToStep","@id":"https://neuroanswers.net/c/web/q/sync-trumbowyg-editors-alpine-js","position":4},{"name":"Refresh toolbar padding or use position:sticky to keep the toolbar visible during internal scrolling","@type":"HowToStep","@id":"https://neuroanswers.net/c/web/q/sync-trumbowyg-editors-alpine-js","position":5},{"name":"Optionally integrate the lightweight syncscroll library to scale syncing to multiple panes","@type":"HowToStep","@id":"https://neuroanswers.net/c/web/q/sync-trumbowyg-editors-alpine-js","position":6}],"@type":"HowTo","@context":"https://schema.org","description":"Step-by-step instructions to synchronize internal vertical scroll of two Trumbowyg instances via Alpine.js or vanilla JavaScript while keeping toolbars visible.","mainEntityOfPage":{"@type":"WebPage","@id":"https://neuroanswers.net/c/web/q/sync-trumbowyg-editors-alpine-js"},"inLanguage":"en","dateCreated":"2026-01-05T16:35:48.554Z","datePublished":"2026-01-05T16:35:48.554Z","dateModified":"2026-01-05T16:35:48.554Z","author":[{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}}],"publisher":{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}},"@id":"https://neuroanswers.net/c/web/q/sync-trumbowyg-editors-alpine-js","url":"https://neuroanswers.net/c/web/q/sync-trumbowyg-editors-alpine-js"},{"@type":"CollectionPage","@id":"https://neuroanswers.net/c/web/q/sync-trumbowyg-editors-alpine-js/#related-questions","name":"Alpine.js: Sync Trumbowyg Vertical Scrolling with Code","description":"Sync vertical scrolling between two Trumbowyg editors using Alpine.js or vanilla JS. Keeps toolbars visible and avoids page scrolling. Includes runnable code.","url":"https://neuroanswers.net/c/web/q/sync-trumbowyg-editors-alpine-js","inLanguage":"en","mainEntity":{"@type":"ItemList","@id":"https://neuroanswers.net/c/web/q/sync-trumbowyg-editors-alpine-js/#related-questions","itemListElement":[{"@type":"ListItem","@id":"https://neuroanswers.net/c/web/q/fix-slow-css-js-hostinger-pagespeed-insights","name":"Fix Slow CSS/JS on Hostinger: Boost PageSpeed Score","position":1,"item":{"@type":"Article","@id":"https://neuroanswers.net/c/web/q/fix-slow-css-js-hostinger-pagespeed-insights","mainEntityOfPage":{"@type":"WebPage","@id":"https://neuroanswers.net/c/web/q/fix-slow-css-js-hostinger-pagespeed-insights"},"inLanguage":"en","dateCreated":"2026-01-01T15:40:49.234Z","datePublished":"2026-01-01T15:40:49.234Z","dateModified":"2026-01-01T15:40:49.234Z","author":[{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}}],"publisher":{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}},"headline":"Fix Slow CSS/JS on Hostinger: Boost PageSpeed Score","description":"Diagnose why PageSpeed Insights flags slow CSS/JS on Hostinger sites. Improve Speed Index with CDN, minification, caching, Brotli, critical CSS, defer JS, and TTFB fixes for better performance.","keywords":["pagespeed insights","hostinger","slow css js","speed index","ttfb","cdn","css optimization","js optimization","litespeed cache","minify css","server response time","web performance"],"image":[],"articleBody":""}},{"@type":"ListItem","@id":"https://neuroanswers.net/c/web/q/fix-svg-text-editor-partial-formatting-tspan","name":"Fix SVG Text Editor: Partial Formatting with tspan","position":2,"item":{"@type":"Article","@id":"https://neuroanswers.net/c/web/q/fix-svg-text-editor-partial-formatting-tspan","mainEntityOfPage":{"@type":"WebPage","@id":"https://neuroanswers.net/c/web/q/fix-svg-text-editor-partial-formatting-tspan"},"inLanguage":"en","dateCreated":"2025-10-18T23:09:39.604Z","datePublished":"2025-10-18T23:09:39.604Z","dateModified":"2025-12-29T19:16:59.830Z","author":[{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}}],"publisher":{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}},"headline":"Fix SVG Text Editor: Partial Formatting with tspan","description":"Fix JavaScript SVG text editor issues where formatting applies to entire elements instead of selections. Use tspan for partial styling, prevent text collapse, with step-by-step code using Selection API.","keywords":["svg text editor","partial text formatting","tspan","svg selection","contenteditable overlay","split text into tspan","selection range api","svg text collapse","javascript svg text"],"image":[],"articleBody":""}},{"@type":"ListItem","@id":"https://neuroanswers.net/c/web/q/html-minlength-attribute-why-not-working-alternatives","name":"HTML minlength Attribute: Does It Exist & Why It Fails","position":3,"item":{"@type":"Article","@id":"https://neuroanswers.net/c/web/q/html-minlength-attribute-why-not-working-alternatives","mainEntityOfPage":{"@type":"WebPage","@id":"https://neuroanswers.net/c/web/q/html-minlength-attribute-why-not-working-alternatives"},"inLanguage":"en","dateCreated":"2026-01-29T16:46:12.796Z","datePublished":"2026-01-29T16:46:12.796Z","dateModified":"2026-01-29T16:46:12.796Z","author":[{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}}],"publisher":{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}},"headline":"HTML minlength Attribute: Does It Exist & Why It Fails","description":"Yes, HTML5 has minlength for input fields like text and password. Learn why minlength validation might not work (wrong types, no required), browser support, and alternatives like pattern regex, min attribute, or JavaScript setCustomValidity for robust form checks.","keywords":["html minlength","minlength attribute","input minlength","minlength not working","html form validation","pattern attribute","browser support minlength","javascript validation","html input validation","minlength alternatives"],"image":[],"articleBody":""}},{"@type":"ListItem","@id":"https://neuroanswers.net/c/web/q/elementor-widget-not-working-after-migration","name":"Elementor Widget Not Working After Migration Fix","position":4,"item":{"@type":"Article","@id":"https://neuroanswers.net/c/web/q/elementor-widget-not-working-after-migration","mainEntityOfPage":{"@type":"WebPage","@id":"https://neuroanswers.net/c/web/q/elementor-widget-not-working-after-migration"},"inLanguage":"en","dateCreated":"2025-12-27T10:08:19.809Z","datePublished":"2025-12-27T10:08:19.809Z","dateModified":"2026-02-14T17:29:37.387Z","author":[{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}}],"publisher":{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}},"headline":"Elementor Widget Not Working After Migration Fix","description":"Fix custom Elementor newsbar widget stuck on latest item after server migration. Troubleshoot JS errors, WPCode snippets, server config, caches, and regenerate files to restore sliding effect. Works on staging but not live.","keywords":["elementor widget","elementor migration","custom elementor widget","elementor slider","wordpress migration","wpcode","server configuration","elementor troubleshooting","regenerate css","elementor widgets","javascript error"],"image":[],"articleBody":""}},{"@type":"ListItem","@id":"https://neuroanswers.net/c/web/q/fix-gamepadlistener-not-defined-gamepad-js-local","name":"Fix GamepadListener Not Defined Error in gamepad.js Locally","position":5,"item":{"@type":"Article","@id":"https://neuroanswers.net/c/web/q/fix-gamepadlistener-not-defined-gamepad-js-local","mainEntityOfPage":{"@type":"WebPage","@id":"https://neuroanswers.net/c/web/q/fix-gamepadlistener-not-defined-gamepad-js-local"},"inLanguage":"en","dateCreated":"2026-02-09T14:23:37.954Z","datePublished":"2026-02-09T14:23:37.954Z","dateModified":"2026-02-09T14:23:37.954Z","author":[{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}}],"publisher":{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}},"headline":"Fix GamepadListener Not Defined Error in gamepad.js Locally","description":"Resolve 'GamepadListener is not defined' error when running gamepad.js library demo locally. Learn why Gamepad API needs secure context, how to use local server like Python http.server, and correct destructuring implementation.","keywords":["GamepadListener not defined","gamepad error","gamepad.js","gamepad api","secure context","local server","python http.server","javascript gamepad","navigator.getGamepads"],"image":[],"articleBody":""}},{"@type":"ListItem","@id":"https://neuroanswers.net/c/web/q/prevent-qz-tray-security-popups","name":"Prevent QZ Tray security popups for repeated prints","position":6,"item":{"@type":"Article","@id":"https://neuroanswers.net/c/web/q/prevent-qz-tray-security-popups","mainEntityOfPage":{"@type":"WebPage","@id":"https://neuroanswers.net/c/web/q/prevent-qz-tray-security-popups"},"inLanguage":"en","dateCreated":"2026-01-07T10:42:53.036Z","datePublished":"2026-01-07T10:42:53.036Z","dateModified":"2026-01-07T10:42:53.036Z","author":[{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}}],"publisher":{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}},"headline":"Prevent QZ Tray security popups for repeated prints","description":"Stop QZ Tray security popups: initialize certificate and signature once, keep the WebSocket open, and use server-side signing to avoid prompts for QZ Tray.","keywords":["qz tray","qz tray popups","qz tray certificate","qz tray javascript","qz tray signature","silent printing","server-side signing","websocket connection","print security","prevent popups"],"image":[],"articleBody":""}},{"@type":"ListItem","@id":"https://neuroanswers.net/c/web/q/javascript-function-declarations-vs-expressions","name":"JS Function Declarations vs Expressions: Differences & Use Cases","position":7,"item":{"@type":"Article","@id":"https://neuroanswers.net/c/web/q/javascript-function-declarations-vs-expressions","mainEntityOfPage":{"@type":"WebPage","@id":"https://neuroanswers.net/c/web/q/javascript-function-declarations-vs-expressions"},"inLanguage":"en","dateCreated":"2025-10-19T14:13:28.985Z","datePublished":"2025-10-19T14:13:28.985Z","dateModified":"2025-12-30T16:15:51.172Z","author":[{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}}],"publisher":{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}},"headline":"JS Function Declarations vs Expressions: Differences & Use Cases","description":"Discover key differences between JavaScript function declarations and function expressions. Explore hoisting, pros, cons, var function() {} vs function name() {}, and real-world use cases for better coding.","keywords":["function declaration","function expression","javascript function","function declaration vs expression","javascript hoisting","var function javascript","named function expression","iife javascript","javascript functions"],"image":[],"articleBody":""}},{"@type":"ListItem","@id":"https://neuroanswers.net/c/web/q/css-sticky-toggle-button-sliding-animation-scroll","name":"CSS Sticky Toggle Button: Sliding Animation on Scroll","position":8,"item":{"@type":"Article","@id":"https://neuroanswers.net/c/web/q/css-sticky-toggle-button-sliding-animation-scroll","mainEntityOfPage":{"@type":"WebPage","@id":"https://neuroanswers.net/c/web/q/css-sticky-toggle-button-sliding-animation-scroll"},"inLanguage":"en","dateCreated":"2026-02-06T16:49:41.920Z","datePublished":"2026-02-06T16:49:41.920Z","dateModified":"2026-02-06T16:49:41.920Z","author":[{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}}],"publisher":{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}},"headline":"CSS Sticky Toggle Button: Sliding Animation on Scroll","description":"Create smooth sliding animation for CSS sticky toggle button that fills space when hiding content on scroll (>50px). Flexbox, grid, JS solutions with code for position sticky scroll animation CSS and toggle button CSS.","keywords":["css sticky","scroll animation css","toggle button css","sliding animation toggle button scroll","position sticky","sticky button animation","flexbox height transition","grid template rows animation","animate height auto css","hide content on scroll css","css sticky toggle button"],"image":[],"articleBody":""}},{"@type":"ListItem","@id":"https://neuroanswers.net/c/web/q/javascript-button-not-displaying-fix","name":"JavaScript Button Not Displaying: Fix Document Body Null Issue","position":9,"item":{"@type":"Article","@id":"https://neuroanswers.net/c/web/q/javascript-button-not-displaying-fix","mainEntityOfPage":{"@type":"WebPage","@id":"https://neuroanswers.net/c/web/q/javascript-button-not-displaying-fix"},"inLanguage":"en","dateCreated":"2026-04-15T12:14:19.336Z","datePublished":"2026-04-15T12:14:19.336Z","dateModified":"2026-04-15T13:18:53.349Z","author":[{"@type":"Person","@id":"https://neuroanswers.net/@mdn-contributors","name":"MDN contributors","givenName":"MDN","familyName":"contributors","url":"https://neuroanswers.net/@mdn-contributors","jobTitle":"Technical writer","description":"Community members who write and edit content for MDN Web Docs. The contributors are a global group of volunteers and professionals who collaborate to create and maintain comprehensive documentation for web development technologies."},{"@type":"Organization","@id":"https://neuroanswers.net/@developer.mozilla-org","name":"@developer.mozilla-org","url":"https://neuroanswers.net/@developer.mozilla-org"},{"@type":"Organization","@id":"https://neuroanswers.net/@javascript-info","name":"JavaScript.info","description":"Modern JavaScript Tutorial: simple, but detailed explanations with examples and tasks, including: closures, document and events, object oriented programming and more","url":"https://neuroanswers.net/@javascript-info","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/api/v1/source/javascript-info/logo.png","width":"72","height":"72"}}],"publisher":{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}},"headline":"JavaScript Button Not Displaying: Fix Document Body Null Issue","description":"Learn why your JavaScript button isn't showing on your HTML page and how to fix document body null issues with proper script execution timing.","keywords":["javascript button","javascript button click","document body null","domcontentloaded","javascript append","html button","javascript dom manipulation"],"image":[],"articleBody":""}},{"@type":"ListItem","@id":"https://neuroanswers.net/c/web/q/check-element-visibility-javascript-css-properties","name":"Check Element Visibility with JavaScript: CSS Properties Guide","position":10,"item":{"@type":"Article","@id":"https://neuroanswers.net/c/web/q/check-element-visibility-javascript-css-properties","mainEntityOfPage":{"@type":"WebPage","@id":"https://neuroanswers.net/c/web/q/check-element-visibility-javascript-css-properties"},"inLanguage":"en","dateCreated":"2026-02-09T11:17:18.936Z","datePublished":"2026-02-09T11:17:18.936Z","dateModified":"2026-02-09T11:17:18.936Z","author":[{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}}],"publisher":{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}},"headline":"Check Element Visibility with JavaScript: CSS Properties Guide","description":"Learn how to check if DOM elements are visible using pure JavaScript. Examine display, visibility, opacity and other CSS properties that affect visibility.","keywords":["javascript display","element is visible","check element visible","visibility css","display none css","javascript hidden","hide javascript","css visibility hidden","javascript style display","element visibility javascript","checkvisibility javascript","dom visibility javascript","javascript getComputedStyle","javascript offsetWidth","viewport visibility javascript"],"image":[],"articleBody":""}}]}}]}
Web

Alpine.js: Sync Trumbowyg Vertical Scrolling with Code

Sync vertical scrolling between two Trumbowyg editors using Alpine.js or vanilla JS. Keeps toolbars visible and avoids page scrolling. Includes runnable code.

1 answer 7 views

How can I synchronize vertical scrolling between two Trumbowyg editor instances (two textarea editors) using Alpine.js and vanilla JavaScript so both editors scroll together while keeping each editor’s toolbar visible? Currently my workaround scrolls the page outside the editor containers and hides the toolbars. What is the correct approach or example code to sync the editors’ internal scroll positions for side-by-side text comparison?

Synchronizing vertical scroll between two Trumbowyg editors using Alpine.js is straightforward once you target the right elements—the .trumbowyg-editor contenteditable divs inside each instance, not the hidden textareas. Your current workaround scrolls the page because it’s likely hooking the outer containers; instead, sync the internal scrollTop positions directly with Alpine.js @scroll events or vanilla JavaScript for bidirectional control. This keeps toolbars pinned and visible via Trumbowyg’s fixedBtnPane logic, perfect for side-by-side text comparison without janky page jumps.


Contents


Trumbowyg Scroll Mechanics

Trumbowyg isn’t just a textarea wrapper—under the hood, it’s a full WYSIWYG with a hidden textarea feeding a .trumbowyg-editor div that’s actually scrollable. Why does this trip people up? Most folks target the textarea directly, but that does nothing for visual scrolling. The real action happens in that contenteditable div.

Check the official Trumbowyg documentation—it spells out the structure: toolbar up top (.trumbowyg-button-pane), editable area below (.trumbowyg-editor), and your original textarea stays hidden. Toolbars stay “fixed” thanks to scroll listeners that adjust padding on the box: e.$box.css({paddingTop: toolbarHeight}). Mess with outer scrolls, and boom—toolbars vanish or the page jumps, exactly like your workaround.

For sync scroll between two editors, grab refs to both .trumbowyg-editor elements. Set up listeners on their scroll events. When one fires, mirror scrollTop to the other. Simple? Yeah, but bidirectional means handling both directions without infinite loops—use flags or throttled events.

Ever compared long code diffs side-by-side? This setup shines there, keeping cursors and highlights aligned visually.


Alpine.js Setup for Sync Scroll

Alpine.js makes this declarative and reactive—no jQuery bloat needed. Start with x-data on a parent wrapper holding both editors. Inside, use $refs to access the scrollable divs post-initialization, since Trumbowyg inits them dynamically.

The Alpine.js docs on @scroll are gold: attach @scroll="handleScroll($event)" to each editor container. Browsers smooth-scroll natively, so you get buttery performance. But dots in event names? Use dashes and .dot modifier if needed—@scroll.dot="...".

Here’s the skeleton in your HTML:

html
<div x-data="editorSync()">
 <div x-ref="editor1" class="trumbowyg-editor" @scroll="syncToOther($event, $refs.editor2)"></div>
 <!-- Trumbowyg inits here, injecting the real .trumbowyg-editor -->
</div>

Alpine.js computed values can track scroll positions reactively too—handy if you want to persist or animate. But for raw sync, stick to event handlers. Throttle if content’s massive: scroll events fire like crazy.

Your page-scroll issue? That’s because Trumbowyg’s outer .trumbowyg-box might capture events. Isolate to the inner editor divs.


Vanilla JavaScript Sync Approach

Prefer no frameworks? Vanilla JS nails sync scroll textarea vibes reliably. Grab elements after Trumbowyg inits (use trumbowyg.init callbacks), then bind scroll listeners.

Borrow from classics like the jQuery sync example on DotNetCurry—it sets scrollTop bidirectionally:

javascript
const editor1 = document.querySelector('#editor1 .trumbowyg-editor');
const editor2 = document.querySelector('#editor2 .trumbowyg-editor');

const syncScroll = (source, target) => {
 target.scrollTop = source.scrollTop;
};

editor1.addEventListener('scroll', () => syncScroll(editor1, editor2));
editor2.addEventListener('scroll', () => syncScroll(editor2, editor1));

No loops—user scrolls one, the other follows instantly. Add requestAnimationFrame for smoothness on long content:

javascript
const rafSync = (source, target) => {
 requestAnimationFrame(() => target.scrollTop = source.scrollTop);
};

This mirrors Stack Overflow discussions where folks hit the same textarea-vs-editor snag. Pro tip: init after Trumbowyg loads, or use MutationObserver for dynamic divs.


Full Working Alpine.js Example

Let’s build it. Assume two Trumbowyg instances side-by-side. Alpine.js handles refs cleanly.

HTML setup:

html
<!DOCTYPE html>
<html x-data="editorSync()">
<head>
 <script src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
 <link rel="stylesheet" href="trumbowyg.css">
 <script src="trumbowyg.min.js"></script>
</head>
<body>
 <div class="editors-container">
 <div class="editor-wrapper">
 <textarea id="editor1"></textarea>
 </div>
 <div class="editor-wrapper">
 <textarea id="editor2"></textarea>
 </div>
 </div>

 <script>
 function editorSync() {
 return {
 editor1: null,
 editor2: null,
 init() {
 // Init Trumbowyg first
 $('#editor1').trumbowyg({fixedBtnPane: true});
 $('#editor2').trumbowyg({fixedBtnPane: true});
 
 // Grab scrollables after init
 this.editor1 = document.querySelector('#editor1').closest('.trumbowyg-box').querySelector('.trumbowyg-editor');
 this.editor2 = document.querySelector('#editor2').closest('.trumbowyg-box').querySelector('.trumbowyg-editor');
 
 this.editor1.addEventListener('scroll', () => this.syncScroll(this.editor1, this.editor2));
 this.editor2.addEventListener('scroll', () => this.syncScroll(this.editor2, this.editor1));
 },
 syncScroll(source, target) {
 target.scrollTop = source.scrollTop;
 }
 }
 }
 </script>
</body>
</html>

Boom—scroll one, both move. Alpine.js computed values? Add scrollPos: 0 and $watch('scrollPos', val => { this.editor2.scrollTop = val; }), but events suffice for most.

Tested this? Line up matching text; cursors stay synced. For diffs, highlight changes—scroll locks 'em tight.


Toolbar Visibility and Fixes

Toolbars hiding? Trumbowyg’s fixedBtnPane: true listens to box scrolls, not inner editor ones. Your page scroll workaround triggered outer events, fooling the padding calc.

Fix: Ensure listeners are strictly on .trumbowyg-editor. If toolbars still dip, force the padding refresh:

javascript
const refreshToolbar = (box) => {
 const toolbar = box.querySelector('.trumbowyg-button-pane');
 box.style.paddingTop = toolbar.offsetHeight + 'px';
};

Call after sync. Or disable fixed mode and position manually—position: sticky on toolbars works cross-browser now.

Edge cases? Mobile touch-scrolls lag? Throttle with lodash or native:

javascript
let ticking = false;
function throttledSync(source, target) {
 if (!ticking) {
 requestAnimationFrame(() => {
 target.scrollTop = source.scrollTop;
 ticking = false;
 });
 ticking = true;
 }
}

Sync scroll feels native. Resize windows? Add @resize.window in Alpine to rebind refs.


Advanced Library Option

Want zero boilerplate? Drop in syncscroll library—946 bytes, vanilla JS. Just add class="syncscroll" name="mygroup" to both .trumbowyg-editor divs post-init:

javascript
syncscroll({groups: [{name: 'mygroup'}]});

It handles multi-element sync out-of-box, even horizontal if needed. Like diff2html’s side-by-side, but pure scroll. Integrate with Alpine.js via x-init.

Overkill for two editors? Maybe. But scales if you add more panes. No perf hit—micro indeed.


Sources

  1. Stack Overflow: Sync scroll between 2 textarea editors
  2. GitHub: asvd/syncscroll
  3. .NET Curry: Synchronize Scrolling of Two Multiline TextBoxes
  4. Trumbowyg Documentation
  5. Alpine.js: on directive
  6. GitHub: rtfpessoa/diff2html

Conclusion

Syncing Trumbowyg editors with Alpine.js boils down to targeting .trumbowyg-editor scrollTop—ditch page hacks for internal events, and toolbars stay put. Grab the full example, tweak for your diffs, and you’ll have responsive side-by-side comparison in minutes. Vanilla works too; scale with syncscroll if needed. Questions on alpine js computed values for positions? It’s reactive magic waiting.

Authors
Verified by moderation
NeuroAnswers
Moderation
Alpine.js: Sync Trumbowyg Vertical Scrolling with Code