PageSourceSearch

https://cdn.mtw.ru/mtwru/js/plugins/smoothscroll.js

js mtw.ru collected 2026-09-24 21:18:01 UTC 23,367 bytes, 790 lines download raw bytes

1//
2// SmoothScroll for websites v1.4.10 (Balazs Galambosi)
3// http://www.smoothscroll.net/
4//
5// Licensed under the terms of the MIT license.
6//
7// You may use it in your theme if you credit me. 
8// It is also free to use on any individual website.
9//
10// Exception:
11// The only restriction is to not publish any  
12// extension for browsers or native application
13// without getting a written permission first.
14//
15
16(function () {
17  
18// Scroll Variables (tweakable)
19var defaultOptions = {
20
21    // Scrolling Core
22    frameRate        : 150, // [Hz]
23    animationTime    : 400, // [ms]
24    stepSize         : 100, // [px]
25
26    // Pulse (less tweakable)
27    // ratio of "tail" to "acceleration"
28    pulseAlgorithm   : true,
29    pulseScale       : 4,
30    pulseNormalize   : 1,
31
32    // Acceleration
33    accelerationDelta : 50,  // 50
34    accelerationMax   : 3,   // 3
35
36    // Keyboard Settings
37    keyboardSupport   : true,  // option
38    arrowScroll       : 50,    // [px]
39
40    // Other
41    fixedBackground   : true, 
42    excluded          : ''    
43};
44
45var options = defaultOptions;
46
47
48// Other Variables
49var isExcluded = false;
50var isFrame = false;
51var direction = { x: 0, y: 0 };
52var initDone  = false;
53var root = document.documentElement;
54var activeElement;
55var observer;
56var refreshSize;
57var deltaBuffer = [];
58var deltaBufferTimer;
59var isMac = /^Mac/.test(navigator.platform);
60
61var key = { left: 37, up: 38, right: 39, down: 40, spacebar: 32, 
62            pageup: 33, pagedown: 34, end: 35, home: 36 };
63var arrowKeys = { 37: 1, 38: 1, 39: 1, 40: 1 };
64
65/***********************************************
66 * INITIALIZE
67 ***********************************************/
68
69/**
70 * Tests if smooth scrolling is allowed. Shuts down everything if not.
71 */
72function initTest() {
73    if (options.keyboardSupport) {
74        addEvent('keydown', keydown);
75    }
76}
77
78/**
79 * Sets up scrolls array, determines if frames are involved.
80 */
81function init() {
82  
83    if (initDone || !document.body) return;
84
85    initDone = true;
86
87    var body = document.body;
88    var html = document.documentElement;
89    var windowHeight = window.innerHeight; 
90    var scrollHeight = body.scrollHeight;
91    
92    // check compat mode for root element
93    root = (document.compatMode.indexOf('CSS') >= 0) ? html : body;
94    activeElement = body;
95    
96    initTest();
97
98    // Checks if this script is running in a frame
99    if (top != self) {
100        isFrame = true;
101    }
102
103    /**
104     * Safari 10 fixed it, Chrome fixed it in v45:
105     * This fixes a bug where the areas left and right to 
106     * the content does not trigger the onmousewheel event
107     * on some pages. e.g.: html, body { height: 100% }
108     */
109    else if (isOldSafari &&
110             scrollHeight > windowHeight &&
111            (body.offsetHeight <= windowHeight || 
112             html.offsetHeight <= windowHeight)) {
113
114        var fullPageElem = document.createElement('div');
115        fullPageElem.style.cssText = 'position:absolute; z-index:-10000; ' +
116                                     'top:0; left:0; right:0; height:' + 
117                                      root.scrollHeight + 'px';
118        document.body.appendChild(fullPageElem);
119        
120        // DOM changed (throttled) to fix height
121        var pendingRefresh;
122        refreshSize = function () {
123            if (pendingRefresh) return; // could also be: clearTimeout(pendingRefresh);
124            pendingRefresh = setTimeout(function () {
125                if (isExcluded) return; // could be running after cleanup
126                fullPageElem.style.height = '0';
127                fullPageElem.style.height = root.scrollHeight + 'px';
128                pendingRefresh = null;
129            }, 500); // act rarely to stay fast
130        };
131  
132        setTimeout(refreshSize, 10);
133
134        addEvent('resize', refreshSize);
135
136        // TODO: attributeFilter?
137        var config = {
138            attributes: true, 
139            childList: true, 
140            characterData: false 
141            // subtree: true
142        };
143
144        observer = new MutationObserver(refreshSize);
145        observer.observe(body, config);
146
147        if (root.offsetHeight <= windowHeight) {
148            var clearfix = document.createElement('div');   
149            clearfix.style.clear = 'both';
150            body.appendChild(clearfix);
151        }
152    }
153
154    // disable fixed background
155    if (!options.fixedBackground && !isExcluded) {
156        body.style.backgroundAttachment = 'scroll';
157        html.style.backgroundAttachment = 'scroll';
158    }
159}
160
161/**
162 * Removes event listeners and other traces left on the page.
163 */
164function cleanup() {
165    observer && observer.disconnect();
166    removeEvent(wheelEvent, wheel);
167    removeEvent('mousedown', mousedown);
168    removeEvent('keydown', keydown);
169    removeEvent('resize', refreshSize);
170    removeEvent('load', init);
171}
172
173
174/************************************************
175 * SCROLLING 
176 ************************************************/
177 
178var que = [];
179var pending = false;
180var lastScroll = Date.now();
181
182/**
183 * Pushes scroll actions to the scrolling queue.
184 */
185function scrollArray(elem, left, top) {
186    
187    directionCheck(left, top);
188
189    if (options.accelerationMax != 1) {
190        var now = Date.now();
191        var elapsed = now - lastScroll;
192        if (elapsed < options.accelerationDelta) {
193            var factor = (1 + (50 / elapsed)) / 2;
194            if (factor > 1) {
195                factor = Math.min(factor, options.accelerationMax);
196                left *= factor;
197                top  *= factor;
198            }
199        }
200        lastScroll = Date.now();
201    }          
202    
203    // push a scroll command
204    que.push({
205        x: left, 
206        y: top, 
207        lastX: (left < 0) ? 0.99 : -0.99,
208        lastY: (top  < 0) ? 0.99 : -0.99, 
209        start: Date.now()
210    });
211        
212    // don't act if there's a pending queue
213    if (pending) {
214        return;
215    }  
216
217    var scrollRoot = getScrollRoot();
218    var isWindowScroll = (elem === scrollRoot || elem === document.body);
219    
220    // if we haven't already fixed the behavior, 
221    // and it needs fixing for this sesh
222    if (elem.$scrollBehavior == null && isScrollBehaviorSmooth(elem)) {
223        elem.$scrollBehavior = elem.style.scrollBehavior;
224        elem.style.scrollBehavior = 'auto';
225    }
226
227    var step = function (time) {
228        
229        var now = Date.now();
230        var scrollX = 0;
231        var scrollY = 0; 
232    
233        for (var i = 0; i < que.length; i++) {
234            
235            var item = que[i];
236            var elapsed  = now - item.start;
237            var finished = (elapsed >= options.animationTime);
238            
239            // scroll position: [0, 1]
240            var position = (finished) ? 1 : elapsed / options.animationTime;
241            
242            // easing [optional]
243            if (options.pulseAlgorithm) {
244                position = pulse(position);
245            }
246            
247            // only need the difference
248            var x = (item.x * position - item.lastX) >> 0;
249            var y = (item.y * position - item.lastY) >> 0;
250            
251            // add this to the total scrolling
252            scrollX += x;
253            scrollY += y;            
254            
255            // update last values
256            item.lastX += x;
257            item.lastY += y;
258        
259            // delete and step back if it's over
260            if (finished) {
261                que.splice(i, 1); i--;
262            }           
263        }
264
265        // scroll left and top
266        if (isWindowScroll) {
267            window.scrollBy(scrollX, scrollY);
268        } 
269        else {
270            if (scrollX) elem.scrollLeft += scrollX;
271            if (scrollY) elem.scrollTop  += scrollY;                    
272        }
273        
274        // clean up if there's nothing left to do
275        if (!left && !top) {
276            que = [];
277        }
278        
279        if (que.length) { 
280            requestFrame(step, elem, (1000 / options.frameRate + 1)); 
281        } else { 
282            pending = false;
283            // restore default behavior at the end of scrolling sesh
284            if (elem.$scrollBehavior != null) {
285                elem.style.scrollBehavior = elem.$scrollBehavior;
286                elem.$scrollBehavior = null;
287            }
288        }
289    };
290    
291    // start a new queue of actions
292    requestFrame(step, elem, 0);
293    pending = true;
294}
295
296
297/***********************************************
298 * EVENTS
299 ***********************************************/
300
301/**
302 * Mouse wheel handler.
303 * @param {Object} event
304 */
305function wheel(event) {
306
307    if (!initDone) {
308        init();
309    }
310    
311    var target = event.target;
312
313    // leave early if default action is prevented   
314    // or it's a zooming event with CTRL 
315    if (event.defaultPrevented || event.ctrlKey) {
316        return true;
317    }
318    
319    // leave embedded content alone (flash & pdf)
320    if (isNodeName(activeElement, 'embed') || 
321       (isNodeName(target, 'embed') && /\.pdf/i.test(target.src)) ||
322        isNodeName(activeElement, 'object') ||
323        target.shadowRoot) {
324        return true;
325    }
326
327    var deltaX = -event.wheelDeltaX || event.deltaX || 0;
328    var deltaY = -event.wheelDeltaY || event.deltaY || 0;
329    
330    if (isMac) {
331        if (event.wheelDeltaX && isDivisible(event.wheelDeltaX, 120)) {
332            deltaX = -120 * (event.wheelDeltaX / Math.abs(event.wheelDeltaX));
333        }
334        if (event.wheelDeltaY && isDivisible(event.wheelDeltaY, 120)) {
335            deltaY = -120 * (event.wheelDeltaY / Math.abs(event.wheelDeltaY));
336        }
337    }
338    
339    // use wheelDelta if deltaX/Y is not available
340    if (!deltaX && !deltaY) {
341        deltaY = -event.wheelDelta || 0;
342    }
343
344    // line based scrolling (Firefox mostly)
345    if (event.deltaMode === 1) {
346        deltaX *= 40;
347        deltaY *= 40;
348    }
349
350    var overflowing = overflowingAncestor(target);
351
352    // nothing to do if there's no element that's scrollable
353    if (!overflowing) {
354        // except Chrome iframes seem to eat wheel events, which we need to 
355        // propagate up, if the iframe has nothing overflowing to scroll
356        if (isFrame && isChrome)  {
357            // change target to iframe element itself for the parent frame
358            Object.defineProperty(event, "target", {value: window.frameElement});
359            return parent.wheel(event);
360        }
361        return true;
362    }
363    
364    // check if it's a touchpad scroll that should be ignored
365    if (isTouchpad(deltaY)) {
366        return true;
367    }
368
369    // scale by step size
370    // delta is 120 most of the time
371    // synaptics seems to send 1 sometimes
372    if (Math.abs(deltaX) > 1.2) {
373        deltaX *= options.stepSize / 120;
374    }
375    if (Math.abs(deltaY) > 1.2) {
376        deltaY *= options.stepSize / 120;
377    }
378    
379    scrollArray(overflowing, deltaX, deltaY);
380    event.preventDefault();
381    scheduleClearCache();
382}
383
384/**
385 * Keydown event handler.
386 * @param {Object} event
387 */
388function keydown(event) {
389
390    var target   = event.target;
391    var modifier = event.ctrlKey || event.altKey || event.metaKey || 
392                  (event.shiftKey && event.keyCode !== key.spacebar);
393    
394    // our own tracked active element could've been removed from the DOM
395    if (!document.body.contains(activeElement)) {
396        activeElement = document.activeElement;
397    }
398
399    // do nothing if user is editing text
400    // or using a modifier key (except shift)
401    // or in a dropdown
402    // or inside interactive elements
403    var inputNodeNames = /^(textarea|select|embed|object)$/i;
404    var buttonTypes = /^(button|submit|radio|checkbox|file|color|image)$/i;
405    if ( event.defaultPrevented ||
406         inputNodeNames.test(target.nodeName) ||
407         isNodeName(target, 'input') && !buttonTypes.test(target.type) ||
408         isNodeName(activeElement, 'video') ||
409         isInsideYoutubeVideo(event) ||
410         target.isContentEditable || 
411         modifier ) {
412      return true;
413    }
414
415    // [spacebar] should trigger button press, leave it alone
416    if ((isNodeName(target, 'button') ||
417         isNodeName(target, 'input') && buttonTypes.test(target.type)) &&
418        event.keyCode === key.spacebar) {
419      return true;
420    }
421
422    // [arrwow keys] on radio buttons should be left alone
423    if (isNodeName(target, 'input') && target.type == 'radio' &&
424        arrowKeys[event.keyCode])  {
425      return true;
426    }
427    
428    var shift, x = 0, y = 0;
429    var overflowing = overflowingAncestor(activeElement);
430
431    if (!overflowing) {
432        // Chrome iframes seem to eat key events, which we need to 
433        // propagate up, if the iframe has nothing overflowing to scroll
434        return (isFrame && isChrome) ? parent.keydown(event) : true;
435    }
436
437    var clientHeight = overflowing.clientHeight; 
438
439    if (overflowing == document.body) {
440        clientHeight = window.innerHeight;
441    }
442
443    switch (event.keyCode) {
444        case key.up:
445            y = -options.arrowScroll;
446            break;
447        case key.down:
448            y = options.arrowScroll;
449            break;         
450        case key.spacebar: // (+ shift)
451            shift = event.shiftKey ? 1 : -1;
452            y = -shift * clientHeight * 0.9;
453            break;
454        case key.pageup:
455            y = -clientHeight * 0.9;
456            break;
457        case key.pagedown:
458            y = clientHeight * 0.9;
459            break;
460        case key.home:
461            if (overflowing == document.body && document.scrollingElement)
462                overflowing = document.scrollingElement;
463            y = -overflowing.scrollTop;
464            break;
465        case key.end:
466            var scroll = overflowing.scrollHeight - overflowing.scrollTop;
467            var scrollRemaining = scroll - clientHeight;
468            y = (scrollRemaining > 0) ? scrollRemaining + 10 : 0;
469            break;
470        case key.left:
471            x = -options.arrowScroll;
472            break;
473        case key.right:
474            x = options.arrowScroll;
475            break;            
476        default:
477            return true; // a key we don't care about
478    }
479
480    scrollArray(overflowing, x, y);
481    event.preventDefault();
482    scheduleClearCache();
483}
484
485/**
486 * Mousedown event only for updating activeElement
487 */
488function mousedown(event) {
489    activeElement = event.target;
490}
491
492
493/***********************************************
494 * OVERFLOW
495 ***********************************************/
496
497var uniqueID = (function () {
498    var i = 0;
499    return function (el) {
500        return el.uniqueID || (el.uniqueID = i++);
501    };
502})();
503
504var cacheX = {}; // cleared out after a scrolling session
505var cacheY = {}; // cleared out after a scrolling session
506var clearCacheTimer;
507var smoothBehaviorForElement = {};
508
509//setInterval(function () { cache = {}; }, 10 * 1000);
510
511function scheduleClearCache() {
512    clearTimeout(clearCacheTimer);
513    clearCacheTimer = setInterval(function () { 
514        cacheX = cacheY = smoothBehaviorForElement = {}; 
515    }, 1*1000);
516}
517
518function setCache(elems, overflowing, x) {
519    var cache = x ? cacheX : cacheY;
520    for (var i = elems.length; i--;)
521        cache[uniqueID(elems[i])] = overflowing;
522    return overflowing;
523}
524
525function getCache(el, x) {
526    return (x ? cacheX : cacheY)[uniqueID(el)];
527}
528
529//  (body)                (root)
530//         | hidden | visible | scroll |  auto  |
531// hidden  |   no   |    no   |   YES  |   YES  |
532// visible |   no   |   YES   |   YES  |   YES  |
533// scroll  |   no   |   YES   |   YES  |   YES  |
534// auto    |   no   |   YES   |   YES  |   YES  |
535
536function overflowingAncestor(el) {
537    var elems = [];
538    var body = document.body;
539    var rootScrollHeight = root.scrollHeight;
540    do {
541        var cached = getCache(el, false);
542        if (cached) {
543            return setCache(elems, cached);
544        }
545        elems.push(el);
546        if (rootScrollHeight === el.scrollHeight) {
547            var topOverflowsNotHidden = overflowNotHidden(root) && overflowNotHidden(body);
548            var isOverflowCSS = topOverflowsNotHidden || overflowAutoOrScroll(root);
549            if (isFrame && isContentOverflowing(root) || 
550               !isFrame && isOverflowCSS) {
551                return setCache(elems, getScrollRoot()); 
552            }
553        } else if (isContentOverflowing(el) && overflowAutoOrScroll(el)) {
554            return setCache(elems, el);
555        }
556    } while ((el = el.parentElement));
557}
558
559function isContentOverflowing(el) {
560    return (el.clientHeight + 10 < el.scrollHeight);
561}
562
563// typically for <body> and <html>
564function overflowNotHidden(el) {
565    var overflow = getComputedStyle(el, '').getPropertyValue('overflow-y');
566    return (overflow !== 'hidden');
567}
568
569// for all other elements
570function overflowAutoOrScroll(el) {
571    var overflow = getComputedStyle(el, '').getPropertyValue('overflow-y');
572    return (overflow === 'scroll' || overflow === 'auto');
573}
574
575// for all other elements
576function isScrollBehaviorSmooth(el) {
577    var id = uniqueID(el);
578    if (smoothBehaviorForElement[id] == null) {
579        var scrollBehavior = getComputedStyle(el, '')['scroll-behavior'];
580        smoothBehaviorForElement[id] = ('smooth' == scrollBehavior);
581    }
582    return smoothBehaviorForElement[id];
583}
584
585
586/***********************************************
587 * HELPERS
588 ***********************************************/
589
590function addEvent(type, fn, arg) {
591    window.addEventListener(type, fn, arg || false);
592}
593
594function removeEvent(type, fn, arg) {
595    window.removeEventListener(type, fn, arg || false);  
596}
597
598function isNodeName(el, tag) {
599    return el && (el.nodeName||'').toLowerCase() === tag.toLowerCase();
600}
601
602function directionCheck(x, y) {
603    x = (x > 0) ? 1 : -1;
604    y = (y > 0) ? 1 : -1;
605    if (direction.x !== x || direction.y !== y) {
606        direction.x = x;
607        direction.y = y;
608        que = [];
609        lastScroll = 0;
610    }
611}
612
613if (window.localStorage && localStorage.SS_deltaBuffer) {
614    try { // #46 Safari throws in private browsing for localStorage 
615        deltaBuffer = localStorage.SS_deltaBuffer.split(',');
616    } catch (e) { } 
617}
618
619function isTouchpad(deltaY) {
620    if (!deltaY) return;
621    if (!deltaBuffer.length) {
622        deltaBuffer = [deltaY, deltaY, deltaY];
623    }
624    deltaY = Math.abs(deltaY);
625    deltaBuffer.push(deltaY);
626    deltaBuffer.shift();
627    clearTimeout(deltaBufferTimer);
628    deltaBufferTimer = setTimeout(function () {
629        try { // #46 Safari throws in private browsing for localStorage
630            localStorage.SS_deltaBuffer = deltaBuffer.join(',');
631        } catch (e) { }  
632    }, 1000);
633    var dpiScaledWheelDelta = deltaY > 120 && allDeltasDivisableBy(deltaY); // win64 
634    var tp = !allDeltasDivisableBy(120) && !allDeltasDivisableBy(100) && !dpiScaledWheelDelta;
635    if (deltaY < 50) return true;
636    return tp;
637} 
638
639function isDivisible(n, divisor) {
640    return (Math.floor(n / divisor) == n / divisor);
641}
642
643function allDeltasDivisableBy(divisor) {
644    return (isDivisible(deltaBuffer[0], divisor) &&
645            isDivisible(deltaBuffer[1], divisor) &&
646            isDivisible(deltaBuffer[2], divisor));
647}
648
649function isInsideYoutubeVideo(event) {
650    var elem = event.target;
651    var isControl = false;
652    if (document.URL.indexOf ('www.youtube.com/watch') != -1) {
653        do {
654            isControl = (elem.classList && 
655                         elem.classList.contains('html5-video-controls'));
656            if (isControl) break;
657        } while ((elem = elem.parentNode));
658    }
659    return isControl;
660}
661
662var requestFrame = (function () {
663      return (window.requestAnimationFrame       || 
664              window.webkitRequestAnimationFrame || 
665              window.mozRequestAnimationFrame    ||
666              function (callback, element, delay) {
667                 window.setTimeout(callback, delay || (1000/60));
668             });
669})();
670
671var MutationObserver = (window.MutationObserver || 
672                        window.WebKitMutationObserver ||
673                        window.MozMutationObserver);  
674
675var getScrollRoot = (function() {
676  var SCROLL_ROOT = document.scrollingElement;
677  return function() {
678    if (!SCROLL_ROOT) {
679      var dummy = document.createElement('div');
680      dummy.style.cssText = 'height:10000px;width:1px;';
681      document.body.appendChild(dummy);
682      var bodyScrollTop  = document.body.scrollTop;
683      var docElScrollTop = document.documentElement.scrollTop;
684      window.scrollBy(0, 3);
685      if (document.body.scrollTop != bodyScrollTop)
686        (SCROLL_ROOT = document.body);
687      else 
688        (SCROLL_ROOT = document.documentElement);
689      window.scrollBy(0, -3);
690      document.body.removeChild(dummy);
691    }
692    return SCROLL_ROOT;
693  };
694})();
695
696
697/***********************************************
698 * PULSE (by Michael Herf)
699 ***********************************************/
700 
701/**
702 * Viscous fluid with a pulse for part and decay for the rest.
703 * - Applies a fixed force over an interval (a damped acceleration), and
704 * - Lets the exponential bleed away the velocity over a longer interval
705 * - Michael Herf, http://stereopsis.com/stopping/
706 */
707function pulse_(x) {
708    var val, start, expx;
709    // test
710    x = x * options.pulseScale;
711    if (x < 1) { // acceleartion
712        val = x - (1 - Math.exp(-x));
713    } else {     // tail
714        // the previous animation ended here:
715        start = Math.exp(-1);
716        // simple viscous drag
717        x -= 1;
718        expx = 1 - Math.exp(-x);
719        val = start + (expx * (1 - start));
720    }
721    return val * options.pulseNormalize;
722}
723
724function pulse(x) {
725    if (x >= 1) return 1;
726    if (x <= 0) return 0;
727
728    if (options.pulseNormalize == 1) {
729        options.pulseNormalize /= pulse_(1);
730    }
731    return pulse_(x);
732}
733
734
735/***********************************************
736 * FIRST RUN
737 ***********************************************/
738
739var userAgent = window.navigator.userAgent;
740var isEdge    = /Edge/.test(userAgent); // thank you MS
741var isChrome  = /chrome/i.test(userAgent) && !isEdge; 
742var isSafari  = /safari/i.test(userAgent) && !isEdge; 
743var isMobile  = /mobile/i.test(userAgent);
744var isIEWin7  = /Windows NT 6.1/i.test(userAgent) && /rv:11/i.test(userAgent);
745var isOldSafari = isSafari && (/Version\/8/i.test(userAgent) || /Version\/9/i.test(userAgent));
746var isEnabledForBrowser = (isChrome || isSafari || isIEWin7) && !isMobile;
747
748var supportsPassive = false;
749try {
750  window.addEventListener("test", null, Object.defineProperty({}, 'passive', {
751    get: function () {
752            supportsPassive = true;
753        } 
754    }));
755} catch(e) {}
756
757var wheelOpt = supportsPassive ? { passive: false } : false;
758var wheelEvent = 'onwheel' in document.createElement('div') ? 'wheel' : 'mousewheel'; 
759
760if (wheelEvent && isEnabledForBrowser) {
761    addEvent(wheelEvent, wheel, wheelOpt);
762    addEvent('mousedown', mousedown);
763    addEvent('load', init);
764}
765
766
767/***********************************************
768 * PUBLIC INTERFACE
769 ***********************************************/
770
771function SmoothScroll(optionsToSet) {
772    for (var key in optionsToSet)
773        if (defaultOptions.hasOwnProperty(key)) 
774            options[key] = optionsToSet[key];
775}
776SmoothScroll.destroy = cleanup;
777
778if (window.SmoothScrollOptions) // async API
779    SmoothScroll(window.SmoothScrollOptions);
780
781if (typeof define === 'function' && define.amd)
782    define(function() {
783        return SmoothScroll;
784    });
785else if ('object' == typeof exports)
786    module.exports = SmoothScroll;
787else
788    window.SmoothScroll = SmoothScroll;
789
790})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.