[{"data":1,"prerenderedAt":2227},["ShallowReactive",2],{"portfolio":3},[4,1722,2014,2171],{"id":5,"title":6,"body":7,"description":1712,"extension":1713,"meta":1714,"navigation":227,"path":1718,"seo":1719,"stem":1720,"__hash__":1721},"oneoff/one-off/1. time.md","Splitting Time",{"type":8,"value":9,"toc":1703},"minimark",[10,14,18,23,33,36,40,43,53,56,59,63,66,69,72,81,84,88,96,99,105,156,161,166,169,257,260,266,280,352,355,378,501,521,547,550,666,669,767,770,787,793,796,835,838,890,893,944,950,953,1003,1009,1019,1046,1052,1062,1227,1252,1265,1287,1293,1296,1416,1422,1425,1435,1441,1444,1463,1476,1480,1483,1486,1559,1569,1607,1617,1620,1699],[11,12,6],"h1",{"id":13},"splitting-time",[15,16,17],"p",{},"A “simple” split-flap display built using pure HTML, CSS, and JavaScript.",[19,20,22],"h2",{"id":21},"what-is-a-split-flap-display","What is a split-flap display?",[15,24,25,26,32],{},"A split flap display is a type of display where each character—or, less commonly, entire words—is split in half vertically on flaps arranged around a drum. The drum rotates, causing the flaps to flip over revealing the next character in the sequence. If none of that makes sense, the this YouTube video from scottbez1 does a pretty good job of explaining it: ",[27,28,29],"a",{"href":29,"rel":30},"https://youtu.be/UAQJJAQSg_g",[31],"nofollow",".",[15,34,35],{},"These were commonly used in transit hubs like train stations and airports to display flight updates or arrival times due to their low power draw and large, modern LCD screens were commercially viable.",[19,37,39],{"id":38},"why","Why?",[15,41,42],{},"I created this project because of two things happening almost simultaneously:",[44,45,46,50],"ul",{},[47,48,49],"li",{},"The YouTube video above randomly showed up in my feed and",[47,51,52],{},"It was a slow Saturday morning and I wanted to see if I could replicate how things looked on my own.",[15,54,55],{},"Short version long: This is possible, but it was a little more complicated than I had originally thought. Because JavaScript is JavaScript, this ended up taking about a week of spare time.",[15,57,58],{},"I also wanted to see the content on the display update on a fairly frequent basis, and since the time is an ever-advancing thing, that's what this will display.",[19,60,62],{"id":61},"the-plan","The plan",[15,64,65],{},"I wanted this display to be flexible (At initialization, I can tell each block in the sequence the characters or words it could display), modular (Easy to plug in anywhere I wanted to plug it into), and mimic the way a real split flap display works.",[15,67,68],{},"That last point is important. Let’s pretend the characters that the display has are “a”, “b”, “c”, and “d”; it is currently displaying “c”; and it needs to get to “b”. Instead of flipping from “c” immediately to “a”, I want it to flip through ”d”, loop around to “a”, before finally landing on “b”.",[15,70,71],{},"At first blush, I thought that I would create two elements—one for the top and one for the bottom—for each character or word that needs to get displayed. I quickly discarded that idea for two reasons:",[73,74,75,78],"ol",{},[47,76,77],{},"I dislike having a bunch of elements in the document that aren’t getting displayed",[47,79,80],{},"Managing and keeping track of the stacking on the top and bottom flaps is going to be challenging",[15,82,83],{},"So onto the next idea: The top and bottom flaps stay static and the transition is handled entirely by a single middle \"flap\" and we cycle out the numbers that are displayed instead.",[19,85,87],{"id":86},"how-it-works","How it works",[15,89,90,91,95],{},"Whether or not this was the ",[92,93,94],"em",{},"correct"," way to do things isn't relevant here, I just wanted to see if it could be done: I created a SplitFlap class that holds a single character display, and everything about that single character can be adjusted through the instance of the class that gets created.",[15,97,98],{},"We'll start by going over what each method in the class does before we go into each function in a line-by-line explanation.",[15,100,101],{},[102,103,104],"code",{},"SplitFlap",[44,106,107,113,119,125,131,138,147],{},[47,108,109,112],{},[102,110,111],{},".constructor(options)",": Takes one argument that is the text options it can display. Sets up the class and because I wanted this to be a low-touch plug-and-play solution it also creates and sets up the elements that will make up the HTML side as well.",[47,114,115,118],{},[102,116,117],{},".setDisplay()",": Syncs the displayed text with the current and next characters to be displayed on the flaps.",[47,120,121,124],{},[102,122,123],{},".getValue()",": Returns the text value of the currently displayed value",[47,126,127,130],{},[102,128,129],{},".nextValue()",": Increments the displayed text by one element in the array of options",[47,132,133,134,137],{},"async ",[102,135,136],{},".flip()",": This is where things start to get a little bit complicated. This method returns a promise that gets resolved once the flip animation it starts, ends.",[47,139,133,140,143,144,146],{},[102,141,142],{},".flipTo(value)",": This one repeatedly calls ",[102,145,136],{}," until the requested value is displayed",[47,148,149,152,153,32],{},[102,150,151],{},".appendTo(parentElement)",": I'm not particularly proud of this one. It appends the elements created in the constructor to the passed in ",[102,154,155],{},"parentElement",[157,158,160],"h3",{"id":159},"starting-from-the-top","Starting from the top",[162,163,165],"h4",{"id":164},"setting-the-private-properties","Setting the private properties",[15,167,168],{},"First, we set a number of private properties. Because of the way JavaScript (currently) works private properties have to be pre-initialized to some value",[170,171,176],"pre",{"className":172,"code":173,"language":174,"meta":175,"style":175},"language-js shiki shiki-themes github-dark","#options = [];\n#currentValue = 0;\n#display = null;\n\n#dynamic = null;\n\n#flaps = {};\n","js","",[102,177,178,194,209,222,229,241,246],{"__ignoreMap":175},[179,180,183,187,191],"span",{"class":181,"line":182},"line",1,[179,184,186],{"class":185},"s95oV","#options ",[179,188,190],{"class":189},"snl16","=",[179,192,193],{"class":185}," [];\n",[179,195,197,200,202,206],{"class":181,"line":196},2,[179,198,199],{"class":185},"#currentValue ",[179,201,190],{"class":189},[179,203,205],{"class":204},"sDLfK"," 0",[179,207,208],{"class":185},";\n",[179,210,212,215,217,220],{"class":181,"line":211},3,[179,213,214],{"class":185},"#display ",[179,216,190],{"class":189},[179,218,219],{"class":204}," null",[179,221,208],{"class":185},[179,223,225],{"class":181,"line":224},4,[179,226,228],{"emptyLinePlaceholder":227},true,"\n",[179,230,232,235,237,239],{"class":181,"line":231},5,[179,233,234],{"class":185},"#dynamic ",[179,236,190],{"class":189},[179,238,219],{"class":204},[179,240,208],{"class":185},[179,242,244],{"class":181,"line":243},6,[179,245,228],{"emptyLinePlaceholder":227},[179,247,249,252,254],{"class":181,"line":248},7,[179,250,251],{"class":185},"#flaps ",[179,253,190],{"class":189},[179,255,256],{"class":185}," {};\n",[15,258,259],{},"They'll make more sense later.",[162,261,263],{"id":262},"constructoroptions",[102,264,265],{},"constructor(options)",[15,267,268,269,272,273,276,277,279],{},"First things first, we check to see if the passed in ",[102,270,271],{},"options"," is an array, and throw an error if it isn't. If it is an array we set the private ",[102,274,275],{},"#options"," property to the ",[102,278,271],{}," that are passed in.",[170,281,283],{"className":172,"code":282,"language":174,"meta":175,"style":175},"if (typeof options != typeof []) {\n    throw new TypeError(\"Variable 'options' is not an array\");\n}\n\nthis.#options = options;\n",[102,284,285,308,330,335,339],{"__ignoreMap":175},[179,286,287,290,293,296,299,302,305],{"class":181,"line":182},[179,288,289],{"class":189},"if",[179,291,292],{"class":185}," (",[179,294,295],{"class":189},"typeof",[179,297,298],{"class":185}," options ",[179,300,301],{"class":189},"!=",[179,303,304],{"class":189}," typeof",[179,306,307],{"class":185}," []) {\n",[179,309,310,313,316,320,323,327],{"class":181,"line":196},[179,311,312],{"class":189},"    throw",[179,314,315],{"class":189}," new",[179,317,319],{"class":318},"svObZ"," TypeError",[179,321,322],{"class":185},"(",[179,324,326],{"class":325},"sU2Wk","\"Variable 'options' is not an array\"",[179,328,329],{"class":185},");\n",[179,331,332],{"class":181,"line":211},[179,333,334],{"class":185},"}\n",[179,336,337],{"class":181,"line":224},[179,338,228],{"emptyLinePlaceholder":227},[179,340,341,344,347,349],{"class":181,"line":231},[179,342,343],{"class":204},"this",[179,345,346],{"class":185},".#options ",[179,348,190],{"class":189},[179,350,351],{"class":185}," options;\n",[15,353,354],{},"Next up: Creating the elements that will make up this single character display. Because I don't know of a better way to do this, it gets a little bit verbose, so bear with me here.",[15,356,357,358,361,362,365,366,369,370,373,374,377],{},"This first group of lines here sets up the elements into local variables that are going to be the flaps. We will be using most of these later to run animations and update values. ",[102,359,360],{},"container"," is what we will be using to hold the character and what we will eventually append to whatever it needs to be. The ",[102,363,364],{},"flap_flap"," element and it's ",[102,367,368],{},"flap_flap_*"," children is what actually does the wiping animation before resetting and updating. ",[102,371,372],{},"top_flap"," and ",[102,375,376],{},"bottom_flap"," don't ever move, but they do get value updates.",[170,379,381],{"className":172,"code":380,"language":174,"meta":175,"style":175},"let container = document.createElement('div');\nlet top_flap = document.createElement('div');\nlet bottom_flap = document.createElement('div');\nlet flap_flap = document.createElement('div');\nlet flap_flap_front = document.createElement('div');\nlet flap_flap_back = document.createElement('div');\n",[102,382,383,406,425,444,463,482],{"__ignoreMap":175},[179,384,385,388,391,393,396,399,401,404],{"class":181,"line":182},[179,386,387],{"class":189},"let",[179,389,390],{"class":185}," container ",[179,392,190],{"class":189},[179,394,395],{"class":185}," document.",[179,397,398],{"class":318},"createElement",[179,400,322],{"class":185},[179,402,403],{"class":325},"'div'",[179,405,329],{"class":185},[179,407,408,410,413,415,417,419,421,423],{"class":181,"line":196},[179,409,387],{"class":189},[179,411,412],{"class":185}," top_flap ",[179,414,190],{"class":189},[179,416,395],{"class":185},[179,418,398],{"class":318},[179,420,322],{"class":185},[179,422,403],{"class":325},[179,424,329],{"class":185},[179,426,427,429,432,434,436,438,440,442],{"class":181,"line":211},[179,428,387],{"class":189},[179,430,431],{"class":185}," bottom_flap ",[179,433,190],{"class":189},[179,435,395],{"class":185},[179,437,398],{"class":318},[179,439,322],{"class":185},[179,441,403],{"class":325},[179,443,329],{"class":185},[179,445,446,448,451,453,455,457,459,461],{"class":181,"line":224},[179,447,387],{"class":189},[179,449,450],{"class":185}," flap_flap ",[179,452,190],{"class":189},[179,454,395],{"class":185},[179,456,398],{"class":318},[179,458,322],{"class":185},[179,460,403],{"class":325},[179,462,329],{"class":185},[179,464,465,467,470,472,474,476,478,480],{"class":181,"line":231},[179,466,387],{"class":189},[179,468,469],{"class":185}," flap_flap_front ",[179,471,190],{"class":189},[179,473,395],{"class":185},[179,475,398],{"class":318},[179,477,322],{"class":185},[179,479,403],{"class":325},[179,481,329],{"class":185},[179,483,484,486,489,491,493,495,497,499],{"class":181,"line":243},[179,485,387],{"class":189},[179,487,488],{"class":185}," flap_flap_back ",[179,490,190],{"class":189},[179,492,395],{"class":185},[179,494,398],{"class":318},[179,496,322],{"class":185},[179,498,403],{"class":325},[179,500,329],{"class":185},[15,502,503,504,506,507,373,510,513,514,373,517,520],{},"Now we just take those elements and make them into the tree shape that we need them to be. ",[102,505,364],{}," gets ",[102,508,509],{},"flap_flap_front",[102,511,512],{},"flap_flap_back"," appended to it because the flap has two sides. The front side is the current value and as it flips, the back side holds the next value. ",[102,515,516],{},"flap_top",[102,518,519],{},"flap_bottom"," are static elements that only fade into mimic getting spun into place like the drum.",[170,522,524],{"className":172,"code":523,"language":174,"meta":175,"style":175},"flap_flap.append(flap_flap_front, flap_flap_back);\ncontainer.append(top_flap, bottom_flap, flap_flap);\n",[102,525,526,537],{"__ignoreMap":175},[179,527,528,531,534],{"class":181,"line":182},[179,529,530],{"class":185},"flap_flap.",[179,532,533],{"class":318},"append",[179,535,536],{"class":185},"(flap_flap_front, flap_flap_back);\n",[179,538,539,542,544],{"class":181,"line":196},[179,540,541],{"class":185},"container.",[179,543,533],{"class":318},[179,545,546],{"class":185},"(top_flap, bottom_flap, flap_flap);\n",[15,548,549],{},"And now for the fun bit: we set up the classes on all of our elements. As much as I wanted this to be a low-touch solution, the CSS side of things is still pretty heavily dependent on the JavaScript being exactly right to display correctly.",[170,551,553],{"className":172,"code":552,"language":174,"meta":175,"style":175},"container.classList.add('dial');\ntop_flap.classList.add('flap', 'top');\nbottom_flap.classList.add('flap', 'bottom');\nflap_flap.classList.add('dynamic');\nflap_flap_front.classList.add('flap', 'front', 'top');\nflap_flap_back.classList.add('flap', 'back', 'bottom');\n",[102,554,555,570,590,608,622,644],{"__ignoreMap":175},[179,556,557,560,563,565,568],{"class":181,"line":182},[179,558,559],{"class":185},"container.classList.",[179,561,562],{"class":318},"add",[179,564,322],{"class":185},[179,566,567],{"class":325},"'dial'",[179,569,329],{"class":185},[179,571,572,575,577,579,582,585,588],{"class":181,"line":196},[179,573,574],{"class":185},"top_flap.classList.",[179,576,562],{"class":318},[179,578,322],{"class":185},[179,580,581],{"class":325},"'flap'",[179,583,584],{"class":185},", ",[179,586,587],{"class":325},"'top'",[179,589,329],{"class":185},[179,591,592,595,597,599,601,603,606],{"class":181,"line":211},[179,593,594],{"class":185},"bottom_flap.classList.",[179,596,562],{"class":318},[179,598,322],{"class":185},[179,600,581],{"class":325},[179,602,584],{"class":185},[179,604,605],{"class":325},"'bottom'",[179,607,329],{"class":185},[179,609,610,613,615,617,620],{"class":181,"line":224},[179,611,612],{"class":185},"flap_flap.classList.",[179,614,562],{"class":318},[179,616,322],{"class":185},[179,618,619],{"class":325},"'dynamic'",[179,621,329],{"class":185},[179,623,624,627,629,631,633,635,638,640,642],{"class":181,"line":231},[179,625,626],{"class":185},"flap_flap_front.classList.",[179,628,562],{"class":318},[179,630,322],{"class":185},[179,632,581],{"class":325},[179,634,584],{"class":185},[179,636,637],{"class":325},"'front'",[179,639,584],{"class":185},[179,641,587],{"class":325},[179,643,329],{"class":185},[179,645,646,649,651,653,655,657,660,662,664],{"class":181,"line":243},[179,647,648],{"class":185},"flap_flap_back.classList.",[179,650,562],{"class":318},[179,652,322],{"class":185},[179,654,581],{"class":325},[179,656,584],{"class":185},[179,658,659],{"class":325},"'back'",[179,661,584],{"class":185},[179,663,605],{"class":325},[179,665,329],{"class":185},[15,667,668],{},"Almost done with the constructor now. Here, we set up the private properties to hold our variables. These will be important for our animations and character updates later.",[170,670,672],{"className":172,"code":671,"language":174,"meta":175,"style":175},"this.#dynamic = flap_flap;\n\nthis.#flaps.top = top_flap;\nthis.#flaps.bottom = bottom_flap;\nthis.#flaps.dynamic = {};\nthis.#flaps.dynamic.front = flap_flap_front;\nthis.#flaps.dynamic.back = flap_flap_back;\n\nthis.#display = container;\n",[102,673,674,686,690,702,714,725,737,749,754],{"__ignoreMap":175},[179,675,676,678,681,683],{"class":181,"line":182},[179,677,343],{"class":204},[179,679,680],{"class":185},".#dynamic ",[179,682,190],{"class":189},[179,684,685],{"class":185}," flap_flap;\n",[179,687,688],{"class":181,"line":196},[179,689,228],{"emptyLinePlaceholder":227},[179,691,692,694,697,699],{"class":181,"line":211},[179,693,343],{"class":204},[179,695,696],{"class":185},".#flaps.top ",[179,698,190],{"class":189},[179,700,701],{"class":185}," top_flap;\n",[179,703,704,706,709,711],{"class":181,"line":224},[179,705,343],{"class":204},[179,707,708],{"class":185},".#flaps.bottom ",[179,710,190],{"class":189},[179,712,713],{"class":185}," bottom_flap;\n",[179,715,716,718,721,723],{"class":181,"line":231},[179,717,343],{"class":204},[179,719,720],{"class":185},".#flaps.dynamic ",[179,722,190],{"class":189},[179,724,256],{"class":185},[179,726,727,729,732,734],{"class":181,"line":243},[179,728,343],{"class":204},[179,730,731],{"class":185},".#flaps.dynamic.front ",[179,733,190],{"class":189},[179,735,736],{"class":185}," flap_flap_front;\n",[179,738,739,741,744,746],{"class":181,"line":248},[179,740,343],{"class":204},[179,742,743],{"class":185},".#flaps.dynamic.back ",[179,745,190],{"class":189},[179,747,748],{"class":185}," flap_flap_back;\n",[179,750,752],{"class":181,"line":751},8,[179,753,228],{"emptyLinePlaceholder":227},[179,755,757,759,762,764],{"class":181,"line":756},9,[179,758,343],{"class":204},[179,760,761],{"class":185},".#display ",[179,763,190],{"class":189},[179,765,766],{"class":185}," container;\n",[15,768,769],{},"And finally we set up the next and displayed values with",[170,771,773],{"className":172,"code":772,"language":174,"meta":175,"style":175},"this.setDisplay();\n",[102,774,775],{"__ignoreMap":175},[179,776,777,779,781,784],{"class":181,"line":182},[179,778,343],{"class":204},[179,780,32],{"class":185},[179,782,783],{"class":318},"setDisplay",[179,785,786],{"class":185},"();\n",[162,788,790],{"id":789},"setdisplay",[102,791,792],{},"setDisplay()",[15,794,795],{},"Performance here isn't a big deal, so we are doing things the long way. I think it makes things a hair more readable. First we get our private properties and throw them into variables. The main goal here is to decrease line length and therefore make everything else more readable.",[170,797,799],{"className":172,"code":798,"language":174,"meta":175,"style":175},"let index = this.#currentValue;\nlet length = this.#options.length;\n",[102,800,801,816],{"__ignoreMap":175},[179,802,803,805,808,810,813],{"class":181,"line":182},[179,804,387],{"class":189},[179,806,807],{"class":185}," index ",[179,809,190],{"class":189},[179,811,812],{"class":204}," this",[179,814,815],{"class":185},".#currentValue;\n",[179,817,818,820,823,825,827,830,833],{"class":181,"line":196},[179,819,387],{"class":189},[179,821,822],{"class":185}," length ",[179,824,190],{"class":189},[179,826,812],{"class":204},[179,828,829],{"class":185},".#options.",[179,831,832],{"class":204},"length",[179,834,208],{"class":185},[15,836,837],{},"Next, we get the current and next values from the array of options, wrapping over as needed. The current value is what is displayed, the next value is hidden behind the flap that is currently in the up position.",[170,839,841],{"className":172,"code":840,"language":174,"meta":175,"style":175},"let currentValue = this.#options[index % length];\nlet nextValue = this.#options[(index + 1) % length];\n",[102,842,843,863],{"__ignoreMap":175},[179,844,845,847,850,852,854,857,860],{"class":181,"line":182},[179,846,387],{"class":189},[179,848,849],{"class":185}," currentValue ",[179,851,190],{"class":189},[179,853,812],{"class":204},[179,855,856],{"class":185},".#options[index ",[179,858,859],{"class":189},"%",[179,861,862],{"class":185}," length];\n",[179,864,865,867,870,872,874,877,880,883,886,888],{"class":181,"line":196},[179,866,387],{"class":189},[179,868,869],{"class":185}," nextValue ",[179,871,190],{"class":189},[179,873,812],{"class":204},[179,875,876],{"class":185},".#options[(index ",[179,878,879],{"class":189},"+",[179,881,882],{"class":204}," 1",[179,884,885],{"class":185},") ",[179,887,859],{"class":189},[179,889,862],{"class":185},[15,891,892],{},"Now we need to place the values on the correct faces. With the flap in the \"up\" position, the current value gets displayed on the bottom flap and the front face of the moving flap, and the next value gets displayed on the top flap, and the back side of the moving flap--both of which are hidden because the flapping isn't happening when this method is running.",[170,894,896],{"className":172,"code":895,"language":174,"meta":175,"style":175},"this.#flaps.top.innerHTML = nextValue;\nthis.#flaps.bottom.innerHTML = currentValue;\nthis.#flaps.dynamic.front.innerHTML = currentValue;\nthis.#flaps.dynamic.back.innerHTML = nextValue;\n",[102,897,898,910,922,933],{"__ignoreMap":175},[179,899,900,902,905,907],{"class":181,"line":182},[179,901,343],{"class":204},[179,903,904],{"class":185},".#flaps.top.innerHTML ",[179,906,190],{"class":189},[179,908,909],{"class":185}," nextValue;\n",[179,911,912,914,917,919],{"class":181,"line":196},[179,913,343],{"class":204},[179,915,916],{"class":185},".#flaps.bottom.innerHTML ",[179,918,190],{"class":189},[179,920,921],{"class":185}," currentValue;\n",[179,923,924,926,929,931],{"class":181,"line":211},[179,925,343],{"class":204},[179,927,928],{"class":185},".#flaps.dynamic.front.innerHTML ",[179,930,190],{"class":189},[179,932,921],{"class":185},[179,934,935,937,940,942],{"class":181,"line":224},[179,936,343],{"class":204},[179,938,939],{"class":185},".#flaps.dynamic.back.innerHTML ",[179,941,190],{"class":189},[179,943,909],{"class":185},[162,945,947],{"id":946},"getvalue",[102,948,949],{},"getValue()",[15,951,952],{},"This returns the text representation of the currently displayed value. It's three lines that do pretty much what they say they do, starting with my usually over-verbose local variable initialization.",[170,954,956],{"className":172,"code":955,"language":174,"meta":175,"style":175},"let length = this.#options.length;\nlet index = this.#currentValue;\n\nreturn this.#options[index % length];\n",[102,957,958,974,986,990],{"__ignoreMap":175},[179,959,960,962,964,966,968,970,972],{"class":181,"line":182},[179,961,387],{"class":189},[179,963,822],{"class":185},[179,965,190],{"class":189},[179,967,812],{"class":204},[179,969,829],{"class":185},[179,971,832],{"class":204},[179,973,208],{"class":185},[179,975,976,978,980,982,984],{"class":181,"line":196},[179,977,387],{"class":189},[179,979,807],{"class":185},[179,981,190],{"class":189},[179,983,812],{"class":204},[179,985,815],{"class":185},[179,987,988],{"class":181,"line":211},[179,989,228],{"emptyLinePlaceholder":227},[179,991,992,995,997,999,1001],{"class":181,"line":224},[179,993,994],{"class":189},"return",[179,996,812],{"class":204},[179,998,856],{"class":185},[179,1000,859],{"class":189},[179,1002,862],{"class":185},[162,1004,1006],{"id":1005},"nextvalue",[102,1007,1008],{},"nextValue()",[15,1010,1011,1012,1014,1015,1018],{},"Another short method. Because ",[102,1013,792],{}," handles the wrap-around part with a modulus, we aren't going to worry about doing that here and just naïvely increment the ",[102,1016,1017],{},"#currentValue"," property",[170,1020,1022],{"className":172,"code":1021,"language":174,"meta":175,"style":175},"this.#currentValue++;\nthis.setDisplay();\n",[102,1023,1024,1036],{"__ignoreMap":175},[179,1025,1026,1028,1031,1034],{"class":181,"line":182},[179,1027,343],{"class":204},[179,1029,1030],{"class":185},".#currentValue",[179,1032,1033],{"class":189},"++",[179,1035,208],{"class":185},[179,1037,1038,1040,1042,1044],{"class":181,"line":196},[179,1039,343],{"class":204},[179,1041,32],{"class":185},[179,1043,783],{"class":318},[179,1045,786],{"class":185},[162,1047,133,1049],{"id":1048},"async-flip",[102,1050,1051],{},"flip()",[15,1053,1054,1055,1057,1058,1061],{},"Now that we have all of that administrivia out of the way, ",[102,1056,1051],{}," is what actually drives the whole show. All it does is trigger the animation to flip by adding a class to the ",[102,1059,1060],{},"#display"," property, waits for it to end, and then returns the current value for use later. Making this asynchronous allows us to use promises to reduce browser resource utilization.",[170,1063,1065],{"className":172,"code":1064,"language":174,"meta":175,"style":175},"let returnValue = new Promise((resolve, _reject) => {\n    this.#display.classList.add('flipping');\n\n    this.#dynamic.onanimationend = () => {\n        this.nextValue();\n        this.#display.classList.remove('flipping');\n\n        setTimeout(() => resolve(this.getValue()), 0);\n    };\n});\n\nreturn returnValue;\n",[102,1066,1067,1101,1118,1122,1142,1154,1169,1173,1203,1208,1214,1219],{"__ignoreMap":175},[179,1068,1069,1071,1074,1076,1078,1081,1084,1088,1090,1093,1095,1098],{"class":181,"line":182},[179,1070,387],{"class":189},[179,1072,1073],{"class":185}," returnValue ",[179,1075,190],{"class":189},[179,1077,315],{"class":189},[179,1079,1080],{"class":204}," Promise",[179,1082,1083],{"class":185},"((",[179,1085,1087],{"class":1086},"s9osk","resolve",[179,1089,584],{"class":185},[179,1091,1092],{"class":1086},"_reject",[179,1094,885],{"class":185},[179,1096,1097],{"class":189},"=>",[179,1099,1100],{"class":185}," {\n",[179,1102,1103,1106,1109,1111,1113,1116],{"class":181,"line":196},[179,1104,1105],{"class":204},"    this",[179,1107,1108],{"class":185},".#display.classList.",[179,1110,562],{"class":318},[179,1112,322],{"class":185},[179,1114,1115],{"class":325},"'flipping'",[179,1117,329],{"class":185},[179,1119,1120],{"class":181,"line":211},[179,1121,228],{"emptyLinePlaceholder":227},[179,1123,1124,1126,1129,1132,1135,1138,1140],{"class":181,"line":224},[179,1125,1105],{"class":204},[179,1127,1128],{"class":185},".#dynamic.",[179,1130,1131],{"class":318},"onanimationend",[179,1133,1134],{"class":189}," =",[179,1136,1137],{"class":185}," () ",[179,1139,1097],{"class":189},[179,1141,1100],{"class":185},[179,1143,1144,1147,1149,1152],{"class":181,"line":231},[179,1145,1146],{"class":204},"        this",[179,1148,32],{"class":185},[179,1150,1151],{"class":318},"nextValue",[179,1153,786],{"class":185},[179,1155,1156,1158,1160,1163,1165,1167],{"class":181,"line":243},[179,1157,1146],{"class":204},[179,1159,1108],{"class":185},[179,1161,1162],{"class":318},"remove",[179,1164,322],{"class":185},[179,1166,1115],{"class":325},[179,1168,329],{"class":185},[179,1170,1171],{"class":181,"line":248},[179,1172,228],{"emptyLinePlaceholder":227},[179,1174,1175,1178,1181,1183,1186,1188,1190,1192,1195,1198,1201],{"class":181,"line":751},[179,1176,1177],{"class":318},"        setTimeout",[179,1179,1180],{"class":185},"(() ",[179,1182,1097],{"class":189},[179,1184,1185],{"class":318}," resolve",[179,1187,322],{"class":185},[179,1189,343],{"class":204},[179,1191,32],{"class":185},[179,1193,1194],{"class":318},"getValue",[179,1196,1197],{"class":185},"()), ",[179,1199,1200],{"class":204},"0",[179,1202,329],{"class":185},[179,1204,1205],{"class":181,"line":756},[179,1206,1207],{"class":185},"    };\n",[179,1209,1211],{"class":181,"line":1210},10,[179,1212,1213],{"class":185},"});\n",[179,1215,1217],{"class":181,"line":1216},11,[179,1218,228],{"emptyLinePlaceholder":227},[179,1220,1222,1224],{"class":181,"line":1221},12,[179,1223,994],{"class":189},[179,1225,1226],{"class":185}," returnValue;\n",[15,1228,1229,1230,1233,1234,1236,1237,1240,1241,1243,1244,1247,1248,32],{},"On the first line, we set up a Promise and assign it to ",[102,1231,1232],{},"returnValue",". Promises take an arrow function that it runs using JavaScript magic to decide when to resolve. ",[102,1235,1087],{}," is a function that you can call to say it completed successfully and ",[102,1238,1239],{},"reject"," is what you can call to indicate there was an error in the Promise when it was run. Because we don't use the ",[102,1242,1239],{}," variable, we prepend an ",[102,1245,1246],{},"_"," to it to indicate to VS Code that it isn't used.] The arrow function passed into a Promise gets run immediately after the current function stack is finished. If you are curious about the internals of how that works, this excellent video on the Event Loop in JavaScript should get you caught up: ",[27,1249,1250],{"href":1250,"rel":1251},"https://youtu.be/8aGhZQkoFbQ",[31],[15,1253,1254,1255,1258,1259,1261,1262,1264],{},"Within the function we supply to the Promise, we first append the class ",[102,1256,1257],{},"flipping"," to the ",[102,1260,1060],{}," element that will start the animation once ",[102,1263,1232],{}," gets returned to whatever is waiting.",[15,1266,1267,1268,1271,1272,1275,1276,1278,1279,1282,1283,1286],{},"Then, we attach an ",[102,1269,1270],{},"animationend"," event to the ",[102,1273,1274],{},"#dynamic"," property to first: increment the displayed values, remove the ",[102,1277,1257],{}," class to reset the position, and then return the currently displayed value. Due to how the event loop works, we need to return the value the ",[92,1280,1281],{},"next"," time the Event Loop gets to running Javascript, so we wrap it in a ",[102,1284,1285],{},"setTimeout()"," function with a value of 0.",[162,1288,133,1290],{"id":1289},"async-fliptovalue",[102,1291,1292],{},"flipTo(value)",[15,1294,1295],{},"This one is pretty easy. It resolves once the display has flipped to the value that was passed in.",[170,1297,1299],{"className":172,"code":1298,"language":174,"meta":175,"style":175},"let returnValue = new Promise(async (resolve, _reject) => {\n    let currentValue = this.getValue();\n    while (currentValue != value) {\n        currentValue = await this.flip();\n    }\n    resolve(this.getValue());\n});\n\nreturn returnValue;\n",[102,1300,1301,1332,1349,1362,1381,1386,1402,1406,1410],{"__ignoreMap":175},[179,1302,1303,1305,1307,1309,1311,1313,1315,1318,1320,1322,1324,1326,1328,1330],{"class":181,"line":182},[179,1304,387],{"class":189},[179,1306,1073],{"class":185},[179,1308,190],{"class":189},[179,1310,315],{"class":189},[179,1312,1080],{"class":204},[179,1314,322],{"class":185},[179,1316,1317],{"class":189},"async",[179,1319,292],{"class":185},[179,1321,1087],{"class":1086},[179,1323,584],{"class":185},[179,1325,1092],{"class":1086},[179,1327,885],{"class":185},[179,1329,1097],{"class":189},[179,1331,1100],{"class":185},[179,1333,1334,1337,1339,1341,1343,1345,1347],{"class":181,"line":196},[179,1335,1336],{"class":189},"    let",[179,1338,849],{"class":185},[179,1340,190],{"class":189},[179,1342,812],{"class":204},[179,1344,32],{"class":185},[179,1346,1194],{"class":318},[179,1348,786],{"class":185},[179,1350,1351,1354,1357,1359],{"class":181,"line":211},[179,1352,1353],{"class":189},"    while",[179,1355,1356],{"class":185}," (currentValue ",[179,1358,301],{"class":189},[179,1360,1361],{"class":185}," value) {\n",[179,1363,1364,1367,1369,1372,1374,1376,1379],{"class":181,"line":224},[179,1365,1366],{"class":185},"        currentValue ",[179,1368,190],{"class":189},[179,1370,1371],{"class":189}," await",[179,1373,812],{"class":204},[179,1375,32],{"class":185},[179,1377,1378],{"class":318},"flip",[179,1380,786],{"class":185},[179,1382,1383],{"class":181,"line":231},[179,1384,1385],{"class":185},"    }\n",[179,1387,1388,1391,1393,1395,1397,1399],{"class":181,"line":243},[179,1389,1390],{"class":318},"    resolve",[179,1392,322],{"class":185},[179,1394,343],{"class":204},[179,1396,32],{"class":185},[179,1398,1194],{"class":318},[179,1400,1401],{"class":185},"());\n",[179,1403,1404],{"class":181,"line":248},[179,1405,1213],{"class":185},[179,1407,1408],{"class":181,"line":751},[179,1409,228],{"emptyLinePlaceholder":227},[179,1411,1412,1414],{"class":181,"line":756},[179,1413,994],{"class":189},[179,1415,1226],{"class":185},[15,1417,1418,1419,1421],{},"We start off a lot like we did before, by setting ",[102,1420,1232],{}," to a Promise with the arrow function.",[15,1423,1424],{},"Then, we just get the current value, and if it isn't equal to the value we want to display, we flip through the values until it does match. The slightly (un)intended downside here is that if you want it to flip until you get a \"z\", and all you have it set to be able to display is \"a\", \"b\", \"c\", and \"d\"; it will just flip forever.",[15,1426,1427,1428,1430,1431,1434],{},"Then, once it has landed at the right value, we just return that value. At this point, you may be asking why we don't have a call to ",[102,1429,1285],{}," here like we had before. Either way, the reason is that we already have that taken care of in our calls to ",[102,1432,1433],{},"this.flip()"," and waiting any more time wouldn't be productive.",[162,1436,1438],{"id":1437},"appendtoparentelement",[102,1439,1440],{},"appendTo(parentElement)",[15,1442,1443],{},"This one is just one weird, regrettable line:",[170,1445,1447],{"className":172,"code":1446,"language":174,"meta":175,"style":175},"parentElement.append(this.#display);\n",[102,1448,1449],{"__ignoreMap":175},[179,1450,1451,1454,1456,1458,1460],{"class":181,"line":182},[179,1452,1453],{"class":185},"parentElement.",[179,1455,533],{"class":318},[179,1457,322],{"class":185},[179,1459,343],{"class":204},[179,1461,1462],{"class":185},".#display);\n",[15,1464,1465,1466,1468,1469,1471,1472,1475],{},"We take the ",[102,1467,155],{}," and append the ",[102,1470,1060],{}," property that holds the elements we created in the ",[102,1473,1474],{},"constructor()"," to it.",[19,1477,1479],{"id":1478},"how-do-i-use-it","How do I use it?",[15,1481,1482],{},"This code isn't provided as complete, ready-to-use .js and .css files. You'll either need to assemble it from the code in this page, or pull the class as-is from the script.js and style.css linked from the html page. I do encourage you to take this code as yours to experiment with and improve.",[15,1484,1485],{},"With that in mind, as it is written here to create a single character split flap display that is able to display numbers, all you need to do is:",[170,1487,1489],{"className":172,"code":1488,"language":174,"meta":175,"style":175},"let char = new SplitFlap(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']);\n",[102,1490,1491],{"__ignoreMap":175},[179,1492,1493,1495,1498,1500,1502,1505,1508,1511,1513,1516,1518,1521,1523,1526,1528,1531,1533,1536,1538,1541,1543,1546,1548,1551,1553,1556],{"class":181,"line":182},[179,1494,387],{"class":189},[179,1496,1497],{"class":185}," char ",[179,1499,190],{"class":189},[179,1501,315],{"class":189},[179,1503,1504],{"class":318}," SplitFlap",[179,1506,1507],{"class":185},"([",[179,1509,1510],{"class":325},"'0'",[179,1512,584],{"class":185},[179,1514,1515],{"class":325},"'1'",[179,1517,584],{"class":185},[179,1519,1520],{"class":325},"'2'",[179,1522,584],{"class":185},[179,1524,1525],{"class":325},"'3'",[179,1527,584],{"class":185},[179,1529,1530],{"class":325},"'4'",[179,1532,584],{"class":185},[179,1534,1535],{"class":325},"'5'",[179,1537,584],{"class":185},[179,1539,1540],{"class":325},"'6'",[179,1542,584],{"class":185},[179,1544,1545],{"class":325},"'7'",[179,1547,584],{"class":185},[179,1549,1550],{"class":325},"'8'",[179,1552,584],{"class":185},[179,1554,1555],{"class":325},"'9'",[179,1557,1558],{"class":185},"]);\n",[15,1560,1561,1562,1564,1565,1568],{},"Then, let's pretend your application counts from 0 to 9, once per second before looping back around. We just need to create an interval to runs once per second and call the ",[102,1563,1051],{}," method on our ",[102,1566,1567],{},"char"," variable. Like so:",[170,1570,1572],{"className":172,"code":1571,"language":174,"meta":175,"style":175},"let interval = setInterval(async () => char.flip(), 1000);\n",[102,1573,1574],{"__ignoreMap":175},[179,1575,1576,1578,1581,1583,1586,1588,1590,1592,1594,1597,1599,1602,1605],{"class":181,"line":182},[179,1577,387],{"class":189},[179,1579,1580],{"class":185}," interval ",[179,1582,190],{"class":189},[179,1584,1585],{"class":318}," setInterval",[179,1587,322],{"class":185},[179,1589,1317],{"class":189},[179,1591,1137],{"class":185},[179,1593,1097],{"class":189},[179,1595,1596],{"class":185}," char.",[179,1598,1378],{"class":318},[179,1600,1601],{"class":185},"(), ",[179,1603,1604],{"class":204},"1000",[179,1606,329],{"class":185},[15,1608,1609,1610,1613,1614,32],{},"If you don't know why we are capturing the return value of ",[102,1611,1612],{},"interval",", it's so that if we need to stop it at some point in time we can do that with ",[102,1615,1616],{},"clearInterval(interval)",[15,1618,1619],{},"Another application for our simple display is to set it to a random value between 0 and 9 every second. Which can be accomplished with:",[170,1621,1623],{"className":172,"code":1622,"language":174,"meta":175,"style":175},"let interval2 = setInterval(asyc () => {\n    let randomValue = (Math.random() * 10).toFixed(0);\n    char.flipTo(randomValue);\n})\n",[102,1624,1625,1647,1683,1694],{"__ignoreMap":175},[179,1626,1627,1629,1632,1634,1636,1638,1641,1643,1645],{"class":181,"line":182},[179,1628,387],{"class":189},[179,1630,1631],{"class":185}," interval2 ",[179,1633,190],{"class":189},[179,1635,1585],{"class":318},[179,1637,322],{"class":185},[179,1639,1640],{"class":318},"asyc",[179,1642,1137],{"class":185},[179,1644,1097],{"class":189},[179,1646,1100],{"class":185},[179,1648,1649,1651,1654,1656,1659,1662,1665,1668,1671,1674,1677,1679,1681],{"class":181,"line":196},[179,1650,1336],{"class":189},[179,1652,1653],{"class":185}," randomValue ",[179,1655,190],{"class":189},[179,1657,1658],{"class":185}," (Math.",[179,1660,1661],{"class":318},"random",[179,1663,1664],{"class":185},"() ",[179,1666,1667],{"class":189},"*",[179,1669,1670],{"class":204}," 10",[179,1672,1673],{"class":185},").",[179,1675,1676],{"class":318},"toFixed",[179,1678,322],{"class":185},[179,1680,1200],{"class":204},[179,1682,329],{"class":185},[179,1684,1685,1688,1691],{"class":181,"line":211},[179,1686,1687],{"class":185},"    char.",[179,1689,1690],{"class":318},"flipTo",[179,1692,1693],{"class":185},"(randomValue);\n",[179,1695,1696],{"class":181,"line":224},[179,1697,1698],{"class":185},"})\n",[1700,1701,1702],"style",{},"html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":175,"searchDepth":196,"depth":196,"links":1704},[1705,1706,1707,1708,1711],{"id":21,"depth":196,"text":22},{"id":38,"depth":196,"text":39},{"id":61,"depth":196,"text":62},{"id":86,"depth":196,"text":87,"children":1709},[1710],{"id":159,"depth":211,"text":160},{"id":1478,"depth":196,"text":1479},"A simple split-flap display using pure HTML, CSS, and JavaScript.","md",{"thumbnail":1715,"alt_text":1716,"draft":1717},"https://assets.quinngale.com/time/time@0.5.jpg","A screenshot of the split-flap time display",false,"/one-off/time",{"title":6,"description":1712},"one-off/1. time","aUL7QtQ7uGnogIPEEZNFlF1S0PIC-MPxFC7-rOHrIBQ",{"id":1723,"title":1724,"body":1725,"description":2006,"extension":1713,"meta":2007,"navigation":227,"path":2010,"seo":2011,"stem":2012,"__hash__":2013},"oneoff/one-off/2. viewport.md","Viewport",{"type":8,"value":1726,"toc":2002},[1727,1730,1733,1736,1738,1741,1744,1746,1749,1987,1999],[11,1728,1724],{"id":1729},"viewport",[15,1731,1732],{},"When building a website or application, it is important to know who you are building it for. Part of that involves getting a good idea of what kind of devices they are using to view to your project: smartphone, desktop computer, laptop, tablet, etc. Each of the items before has a different display size and capability and it heavily impacts how the content gets structured to be readable by your end users.",[15,1734,1735],{},"This project solves almost none of those issues in any sort of automated or scientific way.",[19,1737,39],{"id":38},[15,1739,1740],{},"The idea for this came about when a few of my friends were getting new(-to-them) smartphones at about the same time, and I wanted to know if their phone's displays were significantly different from mine. I also wanted to get a better understanding myself of what sizes to expect to see on mobile devices.",[15,1742,1743],{},"I will sometimes also use this page in my day job to get screenshots of websites in a consistent size for how-to guides in emails for our users.",[19,1745,87],{"id":86},[15,1747,1748],{},"At this point in my Javascript career, I was playing around with scopes and closures and what can and can't be done with them.",[73,1750,1751,1800,1897,1965],{},[47,1752,1753,1754,276,1757,1760,1761,1764,1765],{},"Add an event listener that waits for the page to load to set up the function to display the browser size and the function that updates the page when the viewport window changes size. Adding a ",[102,1755,1756],{},"defer",[102,1758,1759],{},"\u003Cscript>"," tag ",[92,1762,1763],{},"should"," also work in most browsers.",[170,1766,1768],{"className":172,"code":1767,"language":174,"meta":175,"style":175},"window.addEventListener('load', () => {\n    // The rest of the code will go here\n});\n",[102,1769,1770,1790,1796],{"__ignoreMap":175},[179,1771,1772,1775,1778,1780,1783,1786,1788],{"class":181,"line":182},[179,1773,1774],{"class":185},"window.",[179,1776,1777],{"class":318},"addEventListener",[179,1779,322],{"class":185},[179,1781,1782],{"class":325},"'load'",[179,1784,1785],{"class":185},", () ",[179,1787,1097],{"class":189},[179,1789,1100],{"class":185},[179,1791,1792],{"class":181,"line":196},[179,1793,1795],{"class":1794},"sAwPA","    // The rest of the code will go here\n",[179,1797,1798],{"class":181,"line":211},[179,1799,1213],{"class":185},[47,1801,1802,1803,1888,1891,1892,1896],{},"Create the function that will update the output on the HTML side.",[170,1804,1806],{"className":172,"code":1805,"language":174,"meta":175,"style":175},"let updateSize = (widthElement, heightElement, dprElement) => {\n    return () => {\n        widthElement.innerHTML = window.innerWidth;\n        heightElement.innerHTML = window.innerHeight;\n        dprElement.innerHTML = window.devicePixelRatio;\n    };\n};\n",[102,1807,1808,1838,1849,1859,1869,1879,1883],{"__ignoreMap":175},[179,1809,1810,1812,1815,1817,1819,1822,1824,1827,1829,1832,1834,1836],{"class":181,"line":182},[179,1811,387],{"class":189},[179,1813,1814],{"class":318}," updateSize",[179,1816,1134],{"class":189},[179,1818,292],{"class":185},[179,1820,1821],{"class":1086},"widthElement",[179,1823,584],{"class":185},[179,1825,1826],{"class":1086},"heightElement",[179,1828,584],{"class":185},[179,1830,1831],{"class":1086},"dprElement",[179,1833,885],{"class":185},[179,1835,1097],{"class":189},[179,1837,1100],{"class":185},[179,1839,1840,1843,1845,1847],{"class":181,"line":196},[179,1841,1842],{"class":189},"    return",[179,1844,1137],{"class":185},[179,1846,1097],{"class":189},[179,1848,1100],{"class":185},[179,1850,1851,1854,1856],{"class":181,"line":211},[179,1852,1853],{"class":185},"        widthElement.innerHTML ",[179,1855,190],{"class":189},[179,1857,1858],{"class":185}," window.innerWidth;\n",[179,1860,1861,1864,1866],{"class":181,"line":224},[179,1862,1863],{"class":185},"        heightElement.innerHTML ",[179,1865,190],{"class":189},[179,1867,1868],{"class":185}," window.innerHeight;\n",[179,1870,1871,1874,1876],{"class":181,"line":231},[179,1872,1873],{"class":185},"        dprElement.innerHTML ",[179,1875,190],{"class":189},[179,1877,1878],{"class":185}," window.devicePixelRatio;\n",[179,1880,1881],{"class":181,"line":243},[179,1882,1207],{"class":185},[179,1884,1885],{"class":181,"line":248},[179,1886,1887],{"class":185},"};\n",[1889,1890],"br",{},"Every project has a point where they start to go off the rails. Fortunately for us on this project, this happens on line 2. We are using a closure (Help—I don't know what a closure is! ",[27,1893,1894],{"href":1894,"rel":1895},"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures",[31],") on the return line to return a function that just sets the content of the elements that are passed in, and it gets to keep the context of our parent element. This means we only have to pass in the elements once on setup and then we can just call it again to update the numbers.",[47,1898,1899,1900,1903,1904,1907,1908,1955,1957,1958,1961,1962,1964],{},"Use the ",[102,1901,1902],{},"updateSize"," function to the ",[102,1905,1906],{},"window.onresize"," listener, passing in a reference to each HTML element.",[170,1909,1911],{"className":172,"code":1910,"language":174,"meta":175,"style":175},"window.onresize = updateSize(document.getElementById('width'), document.getElementById('height'), document.getElementById('dpr'));\n",[102,1912,1913],{"__ignoreMap":175},[179,1914,1915,1918,1920,1922,1925,1928,1930,1933,1936,1938,1940,1943,1945,1947,1949,1952],{"class":181,"line":182},[179,1916,1917],{"class":185},"window.onresize ",[179,1919,190],{"class":189},[179,1921,1814],{"class":318},[179,1923,1924],{"class":185},"(document.",[179,1926,1927],{"class":318},"getElementById",[179,1929,322],{"class":185},[179,1931,1932],{"class":325},"'width'",[179,1934,1935],{"class":185},"), document.",[179,1937,1927],{"class":318},[179,1939,322],{"class":185},[179,1941,1942],{"class":325},"'height'",[179,1944,1935],{"class":185},[179,1946,1927],{"class":318},[179,1948,322],{"class":185},[179,1950,1951],{"class":325},"'dpr'",[179,1953,1954],{"class":185},"));\n",[1889,1956],{},"That's when the closure kicks in. The ",[102,1959,1960],{},"onresize"," listener will call the returned function, not the ",[102,1963,1902],{}," function every time the viewport's size updates.",[47,1966,1967,1968,1970,1971,1984,1986],{},"We aren't quite finished yet. There's still one last step to go. Because the ",[102,1969,1960],{}," event doesn't get fired until the viewport window changes sizes. Since there's nothing preventing us from calling it like a normal function, we can.",[170,1972,1974],{"className":172,"code":1973,"language":174,"meta":175,"style":175},"window.onresize();\n",[102,1975,1976],{"__ignoreMap":175},[179,1977,1978,1980,1982],{"class":181,"line":182},[179,1979,1774],{"class":185},[179,1981,1960],{"class":318},[179,1983,786],{"class":185},[1889,1985],{},"Now, it will display the viewport size right away when the page loads.",[15,1988,1989,1990,1994,1995,32],{},"You can see everything in action at ",[27,1991,1992],{"href":1992,"rel":1993},"https://lab.quinngale.com/viewport",[31]," or on GitHub at ",[27,1996,1997],{"href":1997,"rel":1998},"https://github.com/quinngale/viewport",[31],[1700,2000,2001],{},"html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":175,"searchDepth":196,"depth":196,"links":2003},[2004,2005],{"id":38,"depth":196,"text":39},{"id":86,"depth":196,"text":87},"A web page that just shows the browser's viewport size.",{"thumbnail":2008,"alt_text":2009,"draft":1717},"https://assets.quinngale.com/viewport/viewport@0.5.jpg","A screenshot of the web page that displays the viewport size","/one-off/viewport",{"title":1724,"description":2006},"one-off/2. viewport","EMQIkkXRppxzg0ye17kr2Y-XDNh5PPYsdDXiXeBGkHo",{"id":2015,"title":2016,"body":2017,"description":2164,"extension":1713,"meta":2165,"navigation":227,"path":2167,"seo":2168,"stem":2169,"__hash__":2170},"oneoff/one-off/3. softbox.md","Softbox",{"type":8,"value":2018,"toc":2159},[2019,2022,2025,2029,2032,2040,2043,2124,2126,2144,2147,2150],[11,2020,2016],{"id":2021},"softbox",[15,2023,2024],{},"This is a page that just colors the background color a solid value, specfied in the anchor part of the URL. If you don't already know what the different parts of a URL are, I'm going to explain them in the section below. If you do, feel free to skip it.",[19,2026,2028],{"id":2027},"anatomy-of-a-url","Anatomy of a URL",[15,2030,2031],{},"We are going to use the following example for the rest of this section and detail\nhow your web browser—probably Firefox, Safari, Edge, or Chrome—uses each part to fetch web addresses from the internet.",[170,2033,2038],{"className":2034,"code":2036,"language":2037},[2035],"language-text","https://lab.quinngale.com/softbox/#663399\n","text",[102,2039,2036],{"__ignoreMap":175},[15,2041,2042],{},"Breaking that into pieces, we have:",[44,2044,2045,2063,2069,2083,2107],{},[47,2046,2047,2050,2051,2054,2055,2058,2059,2062],{},[102,2048,2049],{},"https://",": This is the scheme, or protocol, that your browser uses. 99% of the time it is going to start with ",[102,2052,2053],{},"http"," or more likely ",[102,2056,2057],{},"https",". That tells the browser to prepare to recive HTML code. Another one you may see sometimes is ",[102,2060,2061],{},"mailto",". If things are set up, it will start an email to the email address that follows it.",[47,2064,2065,2068],{},[102,2066,2067],{},"lab",": This is the subdomain. For my domain, I use them as containers of sorts to separate areas of concern. lab.quinngale.com is where I throw all of my experiments, and assets.quinngale.com is where I throw things like images I want to load on websites instead of putting them onto GitHub and slowing things down. Subdomains are arbitrary and there isn't a limit to how many of them can exist on a given domain. They are joined to the domain by a \".\" character.",[47,2070,2071,2074,2075,2078,2079,2082],{},[102,2072,2073],{},"quinngale.com",": This is the domain. ",[102,2076,2077],{},"quinngale"," is a name that belongs to the ",[102,2080,2081],{},"com"," top-level domain. It's almost like a subdomain but not quite.",[47,2084,2085,2088,2089,2091,2092,2095,2096,2098,2099,2102,2103,2106],{},[102,2086,2087],{},"/softbox",": On this web server ",[102,2090,2087],{}," points to the location on the server at ",[102,2093,2094],{},"/var/www/lab.quinngale.com/html/softbox/",". This has been configured by me in the server configuration files. This path only points the server to the ",[102,2097,2021],{}," directory, and stops short of actually naming any files. Not to worry though, the convention is that the server will serve the file ",[102,2100,2101],{},"index.html"," if no file is specified and it exists. Using ",[102,2104,2105],{},"/softbox/index.html"," instead is going to do the exact same thing.",[47,2108,2109,2112,2113,2116,2117,2120,2121,2123],{},[102,2110,2111],{},"#663399",": Formally, anything after the ",[102,2114,2115],{},"#"," is called the anchor and it tells the browser what part of the document to go to. on a normal web page, like Wikipedia, this will automatically link your browser to different headings in the page, like ",[102,2118,2119],{},"#References"," for references that the article uses. To JavaScript, everything at and after the ",[102,2122,2115],{}," character is called the hash, and because I'm primarily a JavaScript person, that's what we are going to call it going forward. And we aren't using it for it's intended purpose.",[19,2125,87],{"id":86},[15,2127,2128,2129,2133,2134,2138,2139,2143],{},"Pass a valid hex color code into the hash (What are hex color codes? ",[27,2130,2131],{"href":2131,"rel":2132},"https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color",[31]," can tell you!) and the included JavaScript first verify that the hex code is valid through a regular expression, and assuming that it passes, it will then set the background color to that hex code. For example, ",[27,2135,2136],{"href":2136,"rel":2137},"https://lab.quinngale.com/softbox#ff0000",[31]," will take you to a bright red background, and ",[27,2140,2141],{"href":2141,"rel":2142},"https://lab.quinngale.com/softbox#5AB7DF",[31]," will take you to a light blue page. Capitalization doesn't matter here. CSS will gladly accept either version. If no hash is specified or it isn't valid, an alert box will tell you and then redirect to a black page (#000000).",[15,2145,2146],{},"Double click to enter into fullscreen mode, double click again or hit escape on your keyboard to exit.",[19,2148,2149],{"id":38},"Why",[15,2151,2152,2153,2155,2156,2158],{},"The hash part of the URL starts with a ",[102,2154,2115],{}," character and hex color codes also start with a ",[102,2157,2115],{}," character. That's the entire line of thought behind creating the page.",{"title":175,"searchDepth":196,"depth":196,"links":2160},[2161,2162,2163],{"id":2027,"depth":196,"text":2028},{"id":86,"depth":196,"text":87},{"id":38,"depth":196,"text":2149},"A simple full-screen page that just displays the hex color passed in the URL.",{"thumbnail":2166,"draft":1717},"https://assets.quinngale.com/softbox/softbox@0.5.jpg","/one-off/softbox",{"title":2016,"description":2164},"one-off/3. softbox","Exnnx5SVeqkyxdi3FAlvcQkjFyZK2kzNlFQRtnnyHxE",{"id":2172,"title":2173,"body":2174,"description":2220,"extension":1713,"meta":2221,"navigation":227,"path":2223,"seo":2224,"stem":2225,"__hash__":2226},"oneoff/one-off/4. terms-of-bad-service.md","Terms of Bad Service",{"type":8,"value":2175,"toc":2217},[2176,2179,2190,2196,2204,2208,2211],[11,2177,2173],{"id":2178},"terms-of-bad-service",[15,2180,2181],{},[27,2182,2185],{"href":2183,"rel":2184},"https://lab.quinngale.com/button-tos",[31],[2186,2187],"img",{"alt":2188,"src":2189},"Screenshot","https://assets.quinngale.com/terms-of-bad-service/terms-of-bad-service.jpg",[15,2191,2192,2193],{},"The link: ",[27,2194,2183],{"href":2183,"rel":2195},[31],[15,2197,2198,2199,2203],{},"This came from a side conversation at work about websites' terms of service and how they are often skipped due to their length and complexity and (at least for the people I've asked) a lack of time to fully read it top to bottom in the time it took to sign up or use the service. This is a ",[2200,2201,2202],"strong",{},"satirical"," attempt at solving only one of those problems: reading the full terms of service. This does not resolve the complexity or time problem.",[19,2205,2207],{"id":2206},"how-this-works","How this works",[15,2209,2210],{},"The system works by presenting your user with the terms of service one randomly-placed button at a time, each containing only one word of the terms (or message) you want them to read. When complete, the user is given a success message and the button to continue with registration (or whatever the next step is) is automatically enabled.",[15,2212,2213],{},[2186,2214],{"alt":2215,"src":2216},"Screen capture","https://assets.quinngale.com/terms-of-bad-service/playthrough.gif",{"title":175,"searchDepth":196,"depth":196,"links":2218},[2219],{"id":2206,"depth":196,"text":2207},"How to guarantee that your users read and understand your website's terms of service.",{"thumbnail":2222,"draft":1717},"https://assets.quinngale.com/terms-of-bad-service/terms-of-bad-service@0.5.jpg","/one-off/terms-of-bad-service",{"title":2173,"description":2220},"one-off/4. terms-of-bad-service","-YZc26irznStwnwLleqDlW2jWomyD8SlEujjlFcndV8",1764741524553]