on Ankidroid, the scrollbar isn’t rendered at all but dragging still controls scrolling. i don’t know how to change it or why is it like that since browsers/ankiweb on android do render the scrollbar. perhaps it’s some kind of android system webview thing. either way, to solve it for ankidroid i added hints.
To set a minimum font-size, use --mjx-min-font-size
. By default it’s 10pt
, but you can treat it like a regular CSS length value as long as it doesn’t depend on the container element (i.e. %). For example:
.card {
--mjx-min-font-size: 7pt;
}
All else are unchanged except for the following:
Demonstration: Front Side
i forgot to mention this before, but the font shrinking operation is run only once when loading the front/back side. that means if you change the viewport without re-rendering the entire thing, the font stays the same - it’s not “responsive”. it’s possible to implement this via resize
event, but i don’t want the font shrinking thing to happen all the time as it causes reflow on each attempt. in particular, if your card have lots of mathjax, i don’t know how slow or fast it would become.
Front Side
{{Front}}
<script>
'use strict';
var device;
var invocation;
var qaElm;
var cssEval;
var fillElm;
function getCSSProperty(elm, prop, default_ = undefined) {
return getComputedStyle(elm).getPropertyValue(prop) || default_;
}
function computeCSSLength (v) {
// See also: https://drafts.csswg.org/cssom/#resolved-values
cssEval.style.height = v;
return getComputedStyle(cssEval).height;
}
function hintMjxContainer(elm) {
if (elm.scrollLeft >= 1) {
elm.classList.add('left-hint');
} else {
elm.classList.remove('left-hint');
}
console.log(`right edge check: ${elm.scrollLeft + elm.clientWidth}px < ${elm.scrollWidth}px`)
if (elm.scrollLeft + elm.clientWidth <= elm.scrollWidth - 2) {
elm.classList.add('right-hint');
} else {
elm.classList.remove('right-hint');
}
}
function fitMjx(elm) {
let right = elm.getBoundingClientRect().right;
let size = parseFloat(getCSSProperty(elm, 'font-size'));
const targetRight = fillElm.getBoundingClientRect().right;
const minSize = parseFloat(computeCSSLength(elm.hasAttribute('data-mjx-min-font-size') ?
elm.getAttribute('data-mjx-min-font-size') :
getCSSProperty(elm, '--mjx-min-font-size', '10pt')));
console.log(`minSize: ${minSize}px`);
if (right <= targetRight) { return; }
// shrink
while (right > targetRight) {
const diff = Math.max(0.1, Math.log10(right - targetRight));
size -= diff * diff;
if (size < minSize) {
size = minSize;
break;
}
elm.style.fontSize = `${size}px`;
right = elm.getBoundingClientRect().right;
}
// grow
while (right <= targetRight) {
size += 1;
elm.style.fontSize = `${size}px`
right = elm.getBoundingClientRect().right;
}
if (right > targetRight) {
size = Math.max(size - 1.5, minSize);
}
elm.style.fontSize = `${size}px`;
const container = elm.parentElement;
hintMjxContainer(container);
container.addEventListener('scroll', (ev) => hintMjxContainer(ev.target));
}
function setup() {
device = 'desktop';
let isAnkiweb = location.hostname.includes('ankiuser.net') || location.hostname.includes('ankiweb.net');
if (isAnkiweb) {
document.documentElement.classList.add('ankiweb');
device = 'ankiweb';
}
if (document.documentElement.classList.contains('mobile')) {
device = 'mobile';
}
qaElm = document.querySelector('#qa');
fillElm = isAnkiweb ? qaElm : document.documentElement;
cssEval = qaElm.querySelector('#css-eval');
}
function main() {
setup();
const mathElms = qaElm.querySelectorAll('mjx-math[display="true"]');
let dispatchDisconnect = false;
if (mathElms.length === 0) {
const observer = new MutationObserver((muts) => {
for (const mut of muts) {
if (mut.type !== "childList") { return; }
for (const node of mut.addedNodes) {
if (node.nodeType === 1 &&
node.tagName === 'MJX-CONTAINER' &&
node.getAttribute('display') === 'true') {
if (dispatchDisconnect === false) {
setTimeout(() => observer.disconnect(), 100);
dispatchDisconnect = true;
}
fitMjx(node.querySelector('mjx-math'));
}
}
}
});
observer.observe(qaElm, { childList: true, subtree: true });
}
mathElms.forEach(fitMjx);
}
if (document.querySelector('.card') !== null) {
if (document.readyState === 'loading') {
console.log('[Init] document is still loading, deferring to "DOMContentLoaded" event');
invocation = 'DOMContentLoaded';
document.addEventListener('DOMContentLoaded', main);
} else {
console.log('[Init] document is ready');
invocation = 'ready';
main();
}
} else {
console.log('[Init] deferring to next macrotask');
invocation = 'macrotask';
setTimeout(main);
}
</script>
<style>
@keyframes appear {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
mjx-math[display="true"] {
animation: appear var(--mjx-appear-timing, 0.2s) ease-in;
}
mjx-container[display="true"] {
--scroll-hint: hsla(0, 0%, 85%, 0.2);
overflow-x: visible;
overflow-y: hidden;
margin-top: 0 !important;
margin-bottom: 0 !important;
padding-top: 1em;
padding-bottom: 1em;
scrollbar-width: thin; /* "all" browsers */
}
.nightMode mjx-container[display="true"] {
--scroll-hint: hsla(0, 0%, 100%, 0.2);
}
mjx-container[display="true"]::-webkit-scrollbar:horizontal {
height: 7px !important; /* chromium and safari */
}
mjx-container[display="true"].left-hint {
background: linear-gradient(to right in oklab, var(--scroll-hint), transparent 5%);
}
mjx-container[display="true"].right-hint {
background: linear-gradient(to left in oklab, var(--scroll-hint), transparent 5%);
}
mjx-container[display="true"].left-hint.right-hint {
background:
linear-gradient(to right in oklab, var(--scroll-hint), transparent 5%),
linear-gradient(to left in oklab, var(--scroll-hint), transparent 5%);
}
</style>
<div id="css-eval" hidden></div>
Demonstration: Styling
My android phone has a different gamma/contrast setting. it’s not configurable. This impacts how the scroll hint gradient is shown. I expose a way to change the hint’s color.
Styling
.card {
font-family: arial;
font-size: 20px;
text-align: center;
color: black;
background-color: white;
}
.mobile :not(.nightMode) {
--scroll-hint: hsla(0, 0%, 65%, 0.2) !important;
}
.mobile .nightMode {
--scroll-hint: hsla(0, 0%, 85%, 0.2) !important;
}