vendor: 12,485 bytes, lines 1-346
1/*! 2 * jQuery & Zepto Lazy - v1.7.5 3 * http://jquery.eisbehr.de/lazy/ 4 * 5 * Copyright 2012 - 2017, Daniel 'Eisbehr' Kern 6 * 7 * Dual licensed under the MIT and GPL-2.0 licenses: 8 * http://www.opensource.org/licenses/mit-license.php 9 * http://www.gnu.org/licenses/gpl-2.0.html 10 * 11 * $("img.lazy").lazy(); 12 */ 13 14;(function(window, undefined) { 15 "use strict"; 16 17 // noinspection JSUnresolvedVariable 18 /** 19 * library instance - here and not in construct to be shorter in minimization 20 * @return void 21 */ 22 var $ = window.jQuery || window.Zepto, 23 24 /** 25 * unique plugin instance id counter 26 * @type {number} 27 */ 28 lazyInstanceId = 0, 29 30 /** 31 * helper to register window load for jQuery 3 32 * @type {boolean} 33 */ 34 windowLoaded = false; 35 36 /** 37 * make lazy available to jquery - and make it a bit more case-insensitive :) 38 * @access public 39 * @type {function} 40 * @param {object} settings 41 * @return void 42 */ 43 $.fn.Lazy = $.fn.lazy = function(settings) { 44 return new LazyPlugin(this, settings); 45 }; 46 47 /** 48 * helper to add plugins to lazy prototype configuration 49 * @access public 50 * @type {function} 51 * @param {string|Array} names 52 * @param {string|Array} [elements] 53 * @param {function} loader 54 * @return void 55 */ 56 $.Lazy = $.lazy = function(names, elements, loader) { 57 // make second parameter optional 58 if( $.isFunction(elements) ) { 59 loader = elements; 60 elements = []; 61 } 62 63 // exit here if parameter is not a callable function 64 if( !$.isFunction(loader) ) return; 65 66 // make parameters an array of names to be sure 67 names = $.isArray(names) ? names : [names]; 68 elements = $.isArray(elements) ? elements : [elements]; 69 70 var config = LazyPlugin.prototype.config, 71 forced = config._f || (config._f = {}); 72 73 // add the loader plugin for every name 74 for( var i = 0, l = names.length; i < l; i++ ) 75 if( config[names[i]] === undefined || $.isFunction(config[names[i]]) ) 76 config[names[i]] = loader; 77 78 // add forced elements loader 79 for( var c = 0, a = elements.length; c < a; c++ ) 80 forced[elements[c]] = names[0]; 81 }; 82 83 /** 84 * contains all logic and the whole element handling 85 * is packed in a private function outside class to reduce memory usage, because it will not be created on every plugin instance 86 * @access private 87 * @type {function} 88 * @param {LazyPlugin} instance 89 * @param {object} config 90 * @param {object|Array} items 91 * @param {object} events 92 * @param {string} namespace 93 * @return void 94 */ 95 function _executeLazy(instance, config, items, events, namespace) { 96 /** 97 * a helper to trigger the 'onFinishedAll' callback after all other events 98 * @access private 99 * @type {number} 100 */ 101 var _awaitingAfterLoad = 0, 102 103 /** 104 * visible content width 105 * @access private 106 * @type {number} 107 */ 108 _actualWidth = -1, 109 110 /** 111 * visible content height 112 * @access private 113 * @type {number} 114 */ 115 _actualHeight = -1, 116 117 /** 118 * determine possibly detected high pixel density 119 * @access private 120 * @type {boolean} 121 */ 122 _isRetinaDisplay = false, 123 124 /** 125 * dictionary entry for better minimization 126 * @access private 127 * @type {string} 128 */ 129 _afterLoad = "afterLoad", 130 131 /** 132 * dictionary entry for better minimization 133 * @access private 134 * @type {string} 135 */ 136 _load = "load", 137 138 /** 139 * dictionary entry for better minimization 140 * @access private 141 * @type {string} 142 */ 143 _error = "error", 144 145 /** 146 * dictionary entry for better minimization 147 * @access private 148 * @type {string} 149 */ 150 _img = "img", 151 152 /** 153 * dictionary entry for better minimization 154 * @access private 155 * @type {string} 156 */ 157 _src = "src", 158 159 /** 160 * dictionary entry for better minimization 161 * @access private 162 * @type {string} 163 */ 164 _srcset = "srcset", 165 166 /** 167 * dictionary entry for better minimization 168 * @access private 169 * @type {string} 170 */ 171 _sizes = "sizes", 172 173 /** 174 * dictionary entry for better minimization 175 * @access private 176 * @type {string} 177 */ 178 _backgroundImage = "background-image"; 179 180 /** 181 * initialize plugin 182 * bind loading to events or set delay time to load all items at once 183 * @access private 184 * @return void 185 */ 186 function _initialize() { 187 // detect actual device pixel ratio 188 // noinspection JSUnresolvedVariable 189 _isRetinaDisplay = window.devicePixelRatio > 1; 190 191 // prepare all initial items 192 _prepareItems(items); 193 194 // if delay time is set load all items at once after delay time 195 if( config.delay >= 0 ) setTimeout(function() { _lazyLoadItems(true); }, config.delay); 196 197 // if no delay is set or combine usage is active bind events 198 if( config.delay < 0 || config.combined ) { 199 // create unique event function 200 events.e = _throttle(config.throttle, function(event) { 201 // reset detected window size on resize event 202 if( event.type === "resize" ) 203 _actualWidth = _actualHeight = -1; 204 205 // execute 'lazy magic' 206 _lazyLoadItems(event.all); 207 }); 208 209 // create function to add new items to instance 210 events.a = function(additionalItems) { 211 _prepareItems(additionalItems); 212 items.push.apply(items, additionalItems); 213 }; 214 215 // create function to get all instance items left 216 events.g = function() { 217 // filter loaded items before return in case internal filter was not running until now 218 return (items = $(items).filter(function() { 219 return !$(this).data(config.loadedName); 220 })); 221 }; 222 223 // create function to force loading elements 224 events.f = function(forcedItems) { 225 for( var i = 0; i < forcedItems.length; i++ ) { 226 // only handle item if available in current instance 227 // use a compare function, because Zepto can't handle object parameter for filter 228 // var item = items.filter(forcedItems[i]); 229 /* jshint loopfunc: true */ 230 var item = items.filter(function() { 231 return this === forcedItems[i]; 232 }); 233 234 if( item.length ) { 235 _lazyLoadItems(false, item); 236 } 237 } 238 }; 239 240 // load initial items 241 _lazyLoadItems(); 242 243 // bind lazy load functions to scroll and resize event 244 // noinspection JSUnresolvedVariable 245 $(config.appendScroll).on("scroll." + namespace + " resize." + namespace, events.e); 246 } 247 } 248 249 /** 250 * prepare items before handle them 251 * @access private 252 * @param {Array|object|jQuery} items 253 * @return void 254 */ 255 function _prepareItems(items) { 256 // fetch used configurations before loops 257 var defaultImage = config.defaultImage, 258 placeholder = config.placeholder, 259 imageBase = config.imageBase, 260 srcsetAttribute = config.srcsetAttribute, 261 loaderAttribute = config.loaderAttribute, 262 forcedTags = config._f || {}; 263 264 // filter items and only add those who not handled yet and got needed attributes available 265 items = $(items).filter(function() { 266 var element = $(this), 267 tag = _getElementTagName(this); 268 269 return !element.data(config.handledName) && 270 (element.attr(config.attribute) || element.attr(srcsetAttribute) || element.attr(loaderAttribute) || forcedTags[tag] !== undefined); 271 }) 272 273 // append plugin instance to all elements 274 .data("plugin_" + config.name, instance); 275 276 for( var i = 0, l = items.length; i < l; i++ ) { 277 var element = $(items[i]), 278 tag = _getElementTagName(items[i]), 279 elementImageBase = element.attr(config.imageBaseAttribute) || imageBase; 280 281 // generate and update source set if an image base is set 282 if( tag === _img && elementImageBase && element.attr(srcsetAttribute) ) 283 element.attr(srcsetAttribute, _getCorrectedSrcSet(element.attr(srcsetAttribute), elementImageBase)); 284 285 // add loader to forced element types 286 if( forcedTags[tag] !== undefined && !element.attr(loaderAttribute) ) 287 element.attr(loaderAttribute, forcedTags[tag]); 288 289 // set default image on every element without source 290 if( tag === _img && defaultImage && !element.attr(_src) ) 291 element.attr(_src, defaultImage); 292 293 // set placeholder on every element without background image 294 else if( tag !== _img && placeholder && (!element.css(_backgroundImage) || element.css(_backgroundImage) === "none") ) 295 element.css(_backgroundImage, "url('" + placeholder + "')"); 296 } 297 } 298 299 /** 300 * the 'lazy magic' - check all items 301 * @access private 302 * @param {boolean} [allItems] 303 * @param {object} [forced] 304 * @return void 305 */ 306 function _lazyLoadItems(allItems, forced) { 307 // skip if no items where left 308 if( !items.length ) { 309 // destroy instance if option is enabled 310 if( config.autoDestroy ) 311 // noinspection JSUnresolvedFunction 312 instance.destroy(); 313 314 return; 315 } 316 317 var elements = forced || items, 318 loadTriggered = false, 319 imageBase = config.imageBase || "", 320 srcsetAttribute = config.srcsetAttribute, 321 handledName = config.handledName; 322 323 // loop all available items 324 for( var i = 0; i < elements.length; i++ ) { 325 // item is at least in loadable area 326 if( allItems || forced || _isInLoadableArea(elements[i]) ) { 327 var element = $(elements[i]), 328 tag = _getElementTagName(elements[i]), 329 attribute = element.attr(config.attribute), 330 elementImageBase = element.attr(config.imageBaseAttribute) || imageBase, 331 customLoader = element.attr(config.loaderAttribute); 332 333 // is not already handled 334 if( !element.data(handledName) && 335 // and is visible or visibility doesn't matter 336 (!config.visibleOnly || element.is(":visible")) && ( 337 // and image source or source set attribute is available 338 (attribute || element.attr(srcsetAttribute)) && ( 339 // and is image tag where attribute is not equal source or source set 340 (tag === _img && (elementImageBase + attribute !== element.attr(_src) || element.attr(srcsetAttribute) !== element.attr(_srcset))) || 341 // or is non image tag where attribute is not equal background 342 (tag !== _img && elementImageBase + attribute !== element.css(_backgroundImage)) 343 ) || 344 // or custom loader is available 345 customLoader )) 346 {
347 // mark element always as handled as this point to prevent double handling 348 loadTriggered = true; 349 element.data(handledName, true); 350 351 // load item 352 _handleItem(element, tag, elementImageBase, customLoader); 353 } 354 } 355 } 356 357 // when something was loaded remove them from remaining items 358 if( loadTriggered ) 359 items = $(items).filter(function() { 360 return !$(this).data(handledName); 361 }); 362 } 363 364 /** 365 * load the given element the lazy way 366 * @access private 367 * @param {object} element 368 * @param {string} tag 369 * @param {string} imageBase 370 * @param {function} [customLoader] 371 * @return void 372 */ 373 function _handleItem(element, tag, imageBase, customLoader) { 374 // increment count of items waiting for after load 375 ++_awaitingAfterLoad; 376 377 // extended error callback for correct 'onFinishedAll' handling 378 var errorCallback = function() { 379 _triggerCallback("onError", element); 380 _reduceAwaiting(); 381 382 // prevent further callback calls 383 errorCallback = $.noop; 384 }; 385 386 // trigger function before loading image 387 _triggerCallback("beforeLoad", element); 388 389 // fetch all double used data here for better code minimization 390 var srcAttribute = config.attribute, 391 srcsetAttribute = config.srcsetAttribute, 392 sizesAttribute = config.sizesAttribute, 393 retinaAttribute = config.retinaAttribute, 394 removeAttribute = config.removeAttribute, 395 loadedName = config.loadedName, 396 elementRetina = element.attr(retinaAttribute); 397 398 // handle custom loader 399 if( customLoader ) { 400 // on load callback 401 var loadCallback = function() { 402 // remove attribute from element 403 if( removeAttribute ) 404 element.removeAttr(config.loaderAttribute); 405 406 // mark element as loaded 407 element.data(loadedName, true); 408 409 // call after load event 410 _triggerCallback(_afterLoad, element); 411 412 // remove item from waiting queue and possibly trigger finished event 413 // it's needed to be asynchronous to run after filter was in _lazyLoadItems 414 setTimeout(_reduceAwaiting, 1); 415 416 // prevent further callback calls 417 loadCallback = $.noop; 418 }; 419 420 // bind error event to trigger callback and reduce waiting amount 421 element.off(_error).one(_error, errorCallback) 422 423 // bind after load callback to element 424 .one(_load, loadCallback); 425 426 // trigger custom loader and handle response 427 if( !_triggerCallback(customLoader, element, function(response) { 428 if( response ) { 429 element.off(_load); 430 loadCallback(); 431 } 432 else { 433 element.off(_error); 434 errorCallback(); 435 } 436 })) element.trigger(_error); 437 } 438 439 // handle images 440 else { 441 // create image object 442 var imageObj = $(new Image()); 443 444 // bind error event to trigger callback and reduce waiting amount 445 imageObj.one(_error, errorCallback) 446 447 // bind after load callback to image 448 .one(_load, function() { 449 // remove element from view 450 element.hide(); 451 452 // set image back to element 453 // do it as single 'attr' calls, to be sure 'src' is set after 'srcset' 454 if( tag === _img ) 455 element.attr(_sizes, imageObj.attr(_sizes)) 456 .attr(_srcset, imageObj.attr(_srcset)) 457 .attr(_src, imageObj.attr(_src)); 458 else 459 element.css(_backgroundImage, "url('" + imageObj.attr(_src) + "')"); 460 461 // bring it back with some effect! 462 element[config.effect](config.effectTime); 463 464 // remove attribute from element 465 if( removeAttribute ) { 466 element.removeAttr(srcAttribute + " " + srcsetAttribute + " " + retinaAttribute + " " + config.imageBaseAttribute); 467 468 // only remove 'sizes' attribute, if it was a custom one 469 if( sizesAttribute !== _sizes ) 470 element.removeAttr(sizesAttribute); 471 } 472 473 // mark element as loaded 474 element.data(loadedName, true); 475 476 // call after load event 477 _triggerCallback(_afterLoad, element); 478 479 // cleanup image object 480 imageObj.remove(); 481 482 // remove item from waiting queue and possibly trigger finished event 483 _reduceAwaiting(); 484 }); 485 486 // set sources 487 // do it as single 'attr' calls, to be sure 'src' is set after 'srcset' 488 var imageSrc = (_isRetinaDisplay && elementRetina ? elementRetina : element.attr(srcAttribute)) || ""; 489 imageObj.attr(_sizes, element.attr(sizesAttribute)) 490 .attr(_srcset, element.attr(srcsetAttribute)) 491 .attr(_src, imageSrc ? imageBase + imageSrc : null); 492 493 // call after load even on cached image 494 imageObj.complete && imageObj.trigger(_load); // jshint ignore : line 495 } 496 } 497 498 /** 499 * check if the given element is inside the current viewport or threshold 500 * @access private 501 * @param {object} element 502 * @return {boolean} 503 */ 504 function _isInLoadableArea(element) { 505 var elementBound = element.getBoundingClientRect(), 506 direction = config.scrollDirection, 507 threshold = config.threshold, 508 vertical = // check if element is in loadable area from top 509 ((_getActualHeight() + threshold) > elementBound.top) && 510 // check if element is even in loadable are from bottom 511 (-threshold < elementBound.bottom), 512 horizontal = // check if element is in loadable area from left 513 ((_getActualWidth() + threshold) > elementBound.left) && 514 // check if element is even in loadable area from right 515 (-threshold < elementBound.right); 516 517 if( direction === "vertical" ) return vertical; 518 else if( direction === "horizontal" ) return horizontal; 519 520 return vertical && horizontal; 521 } 522 523 /** 524 * receive the current viewed width of the browser 525 * @access private 526 * @return {number} 527 */ 528 function _getActualWidth() { 529 return _actualWidth >= 0 ? _actualWidth : (_actualWidth = $(window).width()); 530 } 531 532 /** 533 * receive the current viewed height of the browser 534 * @access private 535 * @return {number} 536 */ 537 function _getActualHeight() { 538 return _actualHeight >= 0 ? _actualHeight : (_actualHeight = $(window).height()); 539 } 540 541 /** 542 * get lowercase tag name of an element 543 * @access private 544 * @param {object} element 545 * @returns {string} 546 */ 547 function _getElementTagName(element) { 548 return element.tagName.toLowerCase(); 549 } 550 551 /** 552 * prepend image base to all srcset entries 553 * @access private 554 * @param {string} srcset 555 * @param {string} imageBase 556 * @returns {string} 557 */ 558 function _getCorrectedSrcSet(srcset, imageBase) { 559 if( imageBase ) { 560 // trim, remove unnecessary spaces and split entries 561 var entries = srcset.split(","); 562 srcset = ""; 563 564 for( var i = 0, l = entries.length; i < l; i++ ) 565 srcset += imageBase + entries[i].trim() + (i !== l - 1 ? "," : ""); 566 } 567 568 return srcset; 569 } 570 571 /** 572 * helper function to throttle down event triggering 573 * @access private 574 * @param {number} delay 575 * @param {function} callback 576 * @return {function} 577 */ 578 function _throttle(delay, callback) { 579 var timeout, 580 lastExecute = 0; 581 582 return function(event, ignoreThrottle) {
583 var elapsed = +new Date() - lastExecute; 584 585 function run() { 586 lastExecute = +new Date(); 587 callback.call(instance, event); 588 } 589 590 timeout && clearTimeout(timeout); // jshint ignore : line 591 592 if( elapsed > delay || !config.enableThrottle || ignoreThrottle ) run(); 593 else timeout = setTimeout(run, delay - elapsed); 594 }; 595 } 596 597 /** 598 * reduce count of awaiting elements to 'afterLoad' event and fire 'onFinishedAll' if reached zero 599 * @access private 600 * @return void 601 */ 602 function _reduceAwaiting() { 603 --_awaitingAfterLoad; 604 605 // if no items were left trigger finished event 606 if( !items.length && !_awaitingAfterLoad ) _triggerCallback("onFinishedAll"); 607 } 608 609 /** 610 * single implementation to handle callbacks, pass element and set 'this' to current instance 611 * @access private 612 * @param {string|function} callback 613 * @param {object} [element] 614 * @param {*} [args] 615 * @return {boolean} 616 */ 617 function _triggerCallback(callback, element, args) { 618 if( (callback = config[callback]) ) { 619 // jQuery's internal '$(arguments).slice(1)' are causing problems at least on old iPads 620 // below is shorthand of 'Array.prototype.slice.call(arguments, 1)' 621 callback.apply(instance, [].slice.call(arguments, 1)); 622 return true; 623 } 624 625 return false; 626 } 627 628 // if event driven or window is already loaded don't wait for page loading 629 if( config.bind === "event" || windowLoaded ) 630 _initialize(); 631 632 // otherwise load initial items and start lazy after page load 633 else // noinspection JSUnresolvedVariable 634 $(window).on(_load + "." + namespace, _initialize); 635 } 636 637 /** 638 * lazy plugin class constructor 639 * @constructor 640 * @access private 641 * @param {object} elements 642 * @param {object} settings 643 * @return {object|LazyPlugin} 644 */ 645 function LazyPlugin(elements, settings) { 646 /** 647 * this lazy plugin instance 648 * @access private 649 * @type {object|LazyPlugin|LazyPlugin.prototype} 650 */ 651 var _instance = this, 652 653 /** 654 * this lazy plugin instance configuration 655 * @access private 656 * @type {object} 657 */ 658 _config = $.extend({}, _instance.config, settings), 659 660 /** 661 * instance generated event executed on container scroll or resize 662 * packed in an object to be referenceable and short named because properties will not be minified 663 * @access private 664 * @type {object} 665 */ 666 _events = {}, 667 668 /** 669 * unique namespace for instance related events 670 * @access private 671 * @type {string} 672 */ 673 _namespace = _config.name + "-" + (++lazyInstanceId); 674 675 // noinspection JSUndefinedPropertyAssignment 676 /** 677 * wrapper to get or set an entry from plugin instance configuration 678 * much smaller on minify as direct access 679 * @access public 680 * @type {function} 681 * @param {string} entryName 682 * @param {*} [value] 683 * @return {LazyPlugin|*} 684 */ 685 _instance.config = function(entryName, value) { 686 if( value === undefined ) 687 return _config[entryName]; 688 689 _config[entryName] = value; 690 return _instance; 691 }; 692 693 // noinspection JSUndefinedPropertyAssignment 694 /** 695 * add additional items to current instance 696 * @access public 697 * @param {Array|object|string} items 698 * @return {LazyPlugin} 699 */ 700 _instance.addItems = function(items) { 701 _events.a && _events.a($.type(items) === "string" ? $(items) : items); // jshint ignore : line 702 return _instance; 703 }; 704 705 // noinspection JSUndefinedPropertyAssignment 706 /** 707 * get all left items of this instance 708 * @access public 709 * @returns {object} 710 */ 711 _instance.getItems = function() { 712 return _events.g ? _events.g() : {}; 713 }; 714 715 // noinspection JSUndefinedPropertyAssignment 716 /** 717 * force lazy to load all items in loadable area right now 718 * by default without throttle 719 * @access public 720 * @type {function} 721 * @param {boolean} [useThrottle] 722 * @return {LazyPlugin} 723 */ 724 _instance.update = function(useThrottle) { 725 _events.e && _events.e({}, !useThrottle); // jshint ignore : line 726 return _instance; 727 }; 728 729 // noinspection JSUndefinedPropertyAssignment 730 /**
vendor: 3,401 bytes, lines 731-830
731 * force element(s) to load directly, ignoring the viewport 732 * @access public 733 * @param {Array|object|string} items 734 * @return {LazyPlugin} 735 */ 736 _instance.force = function(items) { 737 _events.f && _events.f($.type(items) === "string" ? $(items) : items); // jshint ignore : line 738 return _instance; 739 }; 740 741 // noinspection JSUndefinedPropertyAssignment 742 /** 743 * force lazy to load all available items right now 744 * this call ignores throttling 745 * @access public 746 * @type {function} 747 * @return {LazyPlugin} 748 */ 749 _instance.loadAll = function() { 750 _events.e && _events.e({all: true}, true); // jshint ignore : line 751 return _instance; 752 }; 753 754 // noinspection JSUndefinedPropertyAssignment 755 /** 756 * destroy this plugin instance 757 * @access public 758 * @type {function} 759 * @return undefined 760 */ 761 _instance.destroy = function() { 762 // unbind instance generated events 763 // noinspection JSUnresolvedFunction, JSUnresolvedVariable 764 $(_config.appendScroll).off("." + _namespace, _events.e); 765 // noinspection JSUnresolvedVariable 766 $(window).off("." + _namespace); 767 768 // clear events 769 _events = {}; 770 771 return undefined; 772 }; 773 774 // start using lazy and return all elements to be chainable or instance for further use 775 // noinspection JSUnresolvedVariable 776 _executeLazy(_instance, _config, elements, _events, _namespace); 777 return _config.chainable ? elements : _instance; 778 } 779 780 /** 781 * settings and configuration data 782 * @access public 783 * @type {object} 784 */ 785 LazyPlugin.prototype.config = { 786 // general 787 name : "lazy", 788 chainable : true, 789 autoDestroy : true, 790 bind : "load", 791 threshold : 500, 792 visibleOnly : false, 793 appendScroll : window, 794 scrollDirection : "both", 795 imageBase : null, 796 defaultImage : "data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==", 797 placeholder : null, 798 delay : -1, 799 combined : false, 800 801 // attributes 802 attribute : "data-src", 803 srcsetAttribute : "data-srcset", 804 sizesAttribute : "data-sizes", 805 retinaAttribute : "data-retina", 806 loaderAttribute : "data-loader", 807 imageBaseAttribute : "data-imagebase", 808 removeAttribute : true, 809 handledName : "handled", 810 loadedName : "loaded", 811 812 // effect 813 effect : "show", 814 effectTime : 0, 815 816 // throttle 817 enableThrottle : true, 818 throttle : 250, 819 820 // callbacks 821 beforeLoad : undefined, 822 afterLoad : undefined, 823 onError : undefined, 824 onFinishedAll : undefined 825 }; 826 827 // register window load event globally to prevent not loading elements 828 // since jQuery 3.X ready state is fully async and may be executed after 'load' 829 $(window).on("load", function() { windowLoaded = true; }); 830})(window);
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.