<?xml version="1.0" encoding="utf-8" standalone="yes"?><feed xmlns="http://www.w3.org/2005/Atom">
  <title></title>
  <subtitle></subtitle>
  <id>https://www.endpointdev.com/blog/tags/user-interface/</id>
  <link href="https://www.endpointdev.com/blog/tags/user-interface/"/>
  <link href="https://www.endpointdev.com/blog/tags/user-interface/" rel="self"/>
  <updated>2024-03-05T00:00:00+00:00</updated>
  <author>
    <name>End Point Dev</name>
  </author>
  
    <entry>
      <title>Making a Loading Spinner with tkinter</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2024/03/making-a-loading-spinner-with-tkinter/"/>
      <id>https://www.endpointdev.com/blog/2024/03/making-a-loading-spinner-with-tkinter/</id>
      <published>2024-03-05T00:00:00+00:00</published>
      <author>
        <name>Matt Vollrath</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2024/03/making-a-loading-spinner-with-tkinter/spiral-stairs.webp&#34; alt=&#34;An overhead shot of a carpeted spiral staircase, with spiraling railings on either side. The staircase is cut off at the bottom by a wall, so that only half of the circle of stairs is visible. The stairs are enclosed by a semicircular wall, and lit by sunlight streaming through a window on the left. On the right is a window whose view is filled with green leaves.&#34;&gt;&lt;/p&gt;
&lt;!-- Photo by Seth Jensen, 2023. --&gt;
&lt;p&gt;When you need a loading spinner, you really need a loading spinner. Interested in putting something on the screen without installing a pile of dependencies, I reached deep into the toolbox of the Python standard library, dug around a bit, and pulled out the &lt;a href=&#34;https://docs.python.org/3/library/tkinter.html&#34;&gt;tkinter&lt;/a&gt; module.&lt;/p&gt;
&lt;p&gt;The tkinter module is an interface to the venerable Tcl/Tk GUI toolkit, a cross-platform suite for creating user interfaces in the style of whatever operating system you run it on. It&amp;rsquo;s the only built-in GUI toolkit in Python, but there are many worthy alternatives available (see the end of the post for a list).&lt;/p&gt;
&lt;p&gt;Here I&amp;rsquo;ll demonstrate how to make a loading spinnner with tkinter on Ubuntu 22.04. It should work on any platform that runs Python, with some variations when setting up the system for it.&lt;/p&gt;
&lt;h3 id=&#34;prerequisites&#34;&gt;Prerequisites&lt;/h3&gt;
&lt;p&gt;My vision for the loading spinner is some spinning dots and a logo, since this is such a convenient branding opportunity. To accomplish this we&amp;rsquo;ll be extending tkinter with Pillow&amp;rsquo;s ImageTk capability, which can load a PNG with transparency.&lt;/p&gt;
&lt;p&gt;To produce that PNG with transparency, first we may need to rasterize an SVG file, because wise designers work in vectors. This is made trivial by &lt;a href=&#34;https://inkscape.org/&#34;&gt;Inkscape&lt;/a&gt;, a free and complete vector graphics tool:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ inkscape logo.svg -o logo.png&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With the logo in hand, we can move on to setting up our dependencies. Ubuntu&amp;rsquo;s python3 distribution doesn&amp;rsquo;t include tkinter by default, so we&amp;rsquo;ll need to install it explicitly, along with Pillow&amp;rsquo;s separate ImageTk support:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo apt install python3-tk python3-pil.imagetk&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This may occupy up to 75MB, if Python was already installed. This was still the smallest apt footprint of all of the Python GUI libraries in consideration. Pygame was also a strong contender.&lt;/p&gt;
&lt;h3 id=&#34;code&#34;&gt;Code&lt;/h3&gt;
&lt;p&gt;Let&amp;rsquo;s start with something simple: putting the logo on the screen.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;#!/usr/bin/env python3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;PIL&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; Image, ImageTk
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;tkinter&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; BOTH, Canvas, Tk
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# Desired dimensions of our window.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;WIDTH, HEIGHT = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;500&lt;/span&gt;, &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;500&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;__name__&lt;/span&gt; == &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Create the root window object.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    root = Tk()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Create a canvas for drawing our graphics.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    canvas = Canvas(root, width=WIDTH, height=HEIGHT, background=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;black&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Fill the entire window with the canvas.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    canvas.pack(fill=BOTH, expand=&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Load the logo PNG with regular PIL.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    logo_img = Image.open(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;logo.png&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Convert the logo to an ImageTk PhotoImage.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    logo_pi = ImageTk.PhotoImage(logo_img)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Add our logo image to the canvas.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    canvas.create_image(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        WIDTH / &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        HEIGHT / &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        image=logo_pi,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Run the tkinter main loop.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    root.mainloop()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This puts the logo in the center of a window, but the logo may be too large or small. Let&amp;rsquo;s scale it according to the window dimensions, let&amp;rsquo;s say to about ⅔ of the width so we have some room for spinning dots:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;#!/usr/bin/env python3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;PIL&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; Image, ImageTk
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;tkinter&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; BOTH, Canvas, Tk
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# Desired dimensions of our window.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;WIDTH, HEIGHT = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;500&lt;/span&gt;, &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;500&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;__name__&lt;/span&gt; == &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Create the root window object.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    root = Tk()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Create a canvas for drawing our graphics.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    canvas = Canvas(root, width=WIDTH, height=HEIGHT, background=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;black&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Fill the entire window with the canvas.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    canvas.pack(fill=BOTH, expand=&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Load the logo PNG with regular PIL.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    logo_img = Image.open(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;logo.png&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Resize the logo to about 2/3 the window width.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    scaled_w = &lt;span style=&#34;color:#038&#34;&gt;round&lt;/span&gt;(WIDTH * &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0.6&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    scaled_h = &lt;span style=&#34;color:#038&#34;&gt;round&lt;/span&gt;(scaled_w / (logo_img.width / logo_img.height))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    logo_img = logo_img.resize((scaled_w, scaled_h), Image.LANCZOS)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Convert the logo to an ImageTk PhotoImage.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    logo_pi = ImageTk.PhotoImage(logo_img)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Add our logo image to the canvas.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    canvas.create_image(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        WIDTH / &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        HEIGHT / &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        image=logo_pi,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Run the tkinter main loop.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    root.mainloop()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That&amp;rsquo;s better. Now let&amp;rsquo;s add the promised spinning dots. We&amp;rsquo;ll draw some ovals on the canvas and modify our main loop to animate them:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;#!/usr/bin/env python3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;math&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;time&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;PIL&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; Image, ImageTk
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;tkinter&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; BOTH, Canvas, Tk
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# Desired dimensions of our window.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;WIDTH, HEIGHT = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;500&lt;/span&gt;, &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;500&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# Coordinates of the center.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;CENTER_X, CENTER_Y = WIDTH / &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt;, HEIGHT / &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# How many spinning dots we want.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;NUM_DOTS = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;__name__&lt;/span&gt; == &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Create the root window object.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    root = Tk()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Create a canvas for drawing our graphics.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    canvas = Canvas(root, width=WIDTH, height=HEIGHT, background=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;black&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Fill the entire window with the canvas.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    canvas.pack(fill=BOTH, expand=&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Load the logo PNG with regular PIL.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    logo_img = Image.open(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;logo.png&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Resize the logo to about 2/3 the window width.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    scaled_w = &lt;span style=&#34;color:#038&#34;&gt;round&lt;/span&gt;(WIDTH * &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0.6&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    scaled_h = &lt;span style=&#34;color:#038&#34;&gt;round&lt;/span&gt;(scaled_w / (logo_img.width / logo_img.height))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    logo_img = logo_img.resize((scaled_w, scaled_h), Image.LANCZOS)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Convert the logo to an ImageTk PhotoImage.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    logo_pi = ImageTk.PhotoImage(logo_img)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Add our logo image to the canvas.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    canvas.create_image(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        CENTER_X,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        CENTER_Y,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        image=logo_pi,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Radius in pixels of a single dot.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    dot_radius = WIDTH * &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0.05&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Radius of the ring of dots from the center of the window.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    dots_radius = WIDTH / &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt; - dot_radius * &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Helper function to calculate dot position on each update.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#06b;font-weight:bold&#34;&gt;get_dot_coords&lt;/span&gt;(n: &lt;span style=&#34;color:#038&#34;&gt;int&lt;/span&gt;, t: &lt;span style=&#34;color:#038&#34;&gt;float&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&amp;#34;&amp;#34;Get the x0, y0, x1, y1 coords of dot at index &amp;#39;n&amp;#39; at time &amp;#39;t&amp;#39;.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        angle = (n / NUM_DOTS) * math.pi * &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt; + t
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        x = math.cos(angle) * dots_radius + CENTER_X
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        y = math.sin(angle) * dots_radius + CENTER_Y
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; x - dot_radius, y - dot_radius, x + dot_radius, y + dot_radius
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Create all the dots.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    t0 = time.monotonic()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;for&lt;/span&gt; n &lt;span style=&#34;color:#080&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#038&#34;&gt;range&lt;/span&gt;(NUM_DOTS):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        coords = get_dot_coords(n, t0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        canvas.create_oval(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            *coords,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            fill=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;#888888&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            width=&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;,  &lt;span style=&#34;color:#888&#34;&gt;# Border width.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            tags=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;dot_&lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;{&lt;/span&gt;n&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Set up a custom main loop to animate the moving dots.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;while&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;True&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#888&#34;&gt;# Check the time of this update.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        t = time.monotonic()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;for&lt;/span&gt; n &lt;span style=&#34;color:#080&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#038&#34;&gt;range&lt;/span&gt;(NUM_DOTS):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888&#34;&gt;# Get the desired coords for this dot at this time.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            coords = get_dot_coords(n, t)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888&#34;&gt;# Move the dot on the canvas, finding it by its tag.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            canvas.coords(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;dot_&lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;{&lt;/span&gt;n&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                *coords,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#888&#34;&gt;# Call the required tkinter update function.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        root.update()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#888&#34;&gt;# Attempt to stabilize the timing of this loop by targeting 60Hz.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;while&lt;/span&gt; t0 &amp;lt; t:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            t0 += &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt; / &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;60&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        time.sleep(t0 - t)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You may notice that the dots don&amp;rsquo;t look all that great. There&amp;rsquo;s no anti-aliasing when drawing shape primitives in tkinter, so the edges look jagged compared to our well-scaled logo image. One hack is to layer slightly larger and dimmer shapes under each object, which you might do like so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;#!/usr/bin/env python3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;math&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;time&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;PIL&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; Image, ImageTk
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;tkinter&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; BOTH, Canvas, Tk
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# Desired dimensions of our window.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;WIDTH, HEIGHT = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;500&lt;/span&gt;, &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;500&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# Coordinates of the center.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;CENTER_X, CENTER_Y = WIDTH / &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt;, HEIGHT / &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# How many spinning dots we want.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;NUM_DOTS = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# Colors for each layer of fake anti-aliasing around each dot.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# Must be in order from back to front.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;COLORS = [&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;#888888&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;#BBBBBB&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;#FFFFFF&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;__name__&lt;/span&gt; == &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Create the root window object.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    root = Tk()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Create a canvas for drawing our graphics.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    canvas = Canvas(root, width=WIDTH, height=HEIGHT, background=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;black&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Fill the entire window with the canvas.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    canvas.pack(fill=BOTH, expand=&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Load the logo PNG with regular PIL.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    logo_img = Image.open(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;logo.png&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Resize the logo to about 2/3 the window width.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    scaled_w = &lt;span style=&#34;color:#038&#34;&gt;round&lt;/span&gt;(WIDTH * &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0.6&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    scaled_h = &lt;span style=&#34;color:#038&#34;&gt;round&lt;/span&gt;(scaled_w / (logo_img.width / logo_img.height))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    logo_img = logo_img.resize((scaled_w, scaled_h), Image.LANCZOS)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Convert the logo to an ImageTk PhotoImage.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    logo_pi = ImageTk.PhotoImage(logo_img)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Add our logo image to the canvas.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    canvas.create_image(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        CENTER_X,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        CENTER_Y,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        image=logo_pi,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Radius in pixels of a single dot.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    dot_radius = WIDTH * &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0.05&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Radius of the ring of dots from the center of the window.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    dots_radius = WIDTH / &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt; - dot_radius * &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Helper function to calculate dot position on each update.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#06b;font-weight:bold&#34;&gt;get_dot_coords&lt;/span&gt;(n: &lt;span style=&#34;color:#038&#34;&gt;int&lt;/span&gt;, t: &lt;span style=&#34;color:#038&#34;&gt;float&lt;/span&gt;, c: &lt;span style=&#34;color:#038&#34;&gt;int&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&amp;#34;&amp;#34;Get the x0, y0, x1, y1 coords of dot at index &amp;#39;n&amp;#39; at time &amp;#39;t&amp;#39;.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;        Inflate the radius by color index &amp;#39;c&amp;#39;.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        angle = (n / NUM_DOTS) * math.pi * &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt; + t
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        x = math.cos(angle) * dots_radius + CENTER_X
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        y = math.sin(angle) * dots_radius + CENTER_Y
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#888&#34;&gt;# Invert the color index and add to the radius.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        radius = dot_radius + (&lt;span style=&#34;color:#038&#34;&gt;len&lt;/span&gt;(COLORS) - c) * &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0.75&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#888&#34;&gt;#radius = dot_radius + c&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; x - radius, y - radius, x + radius, y + radius
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Create all the dots.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    t0 = time.monotonic()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;for&lt;/span&gt; c, color &lt;span style=&#34;color:#080&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#038&#34;&gt;enumerate&lt;/span&gt;(COLORS):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;for&lt;/span&gt; n &lt;span style=&#34;color:#080&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#038&#34;&gt;range&lt;/span&gt;(NUM_DOTS):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            coords = get_dot_coords(n, t0, c)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            canvas.create_oval(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                *coords,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                fill=color,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                width=&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;,  &lt;span style=&#34;color:#888&#34;&gt;# Border width.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                tags=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;dot_&lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;{&lt;/span&gt;c&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;_&lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;{&lt;/span&gt;n&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Set up a custom main loop to animate the moving dots.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;while&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;True&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#888&#34;&gt;# Check the time of this update.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        t = time.monotonic()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;for&lt;/span&gt; c, color &lt;span style=&#34;color:#080&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#038&#34;&gt;enumerate&lt;/span&gt;(COLORS):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;for&lt;/span&gt; n &lt;span style=&#34;color:#080&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#038&#34;&gt;range&lt;/span&gt;(NUM_DOTS):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#888&#34;&gt;# Get the desired coords for this dot at this time.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                coords = get_dot_coords(n, t, c)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#888&#34;&gt;# Move the dot on the canvas, finding it by its tag.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                canvas.coords(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;dot_&lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;{&lt;/span&gt;c&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;_&lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;{&lt;/span&gt;n&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    *coords,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#888&#34;&gt;# Call the required tkinter update function.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        root.update()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#888&#34;&gt;# Attempt to stabilize the timing of this loop by targeting 60Hz.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;while&lt;/span&gt; t0 &amp;lt; t:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            t0 += &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt; / &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;60&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        time.sleep(t0 - t)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The fake anti-aliasing was a fun exercise, but for this use case you&amp;rsquo;ll probably get better-looking results out of scaling a PNG asset like we did the logo.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2024/03/making-a-loading-spinner-with-tkinter/spinner.webp&#34; alt=&#34;A screenshot of the loading spinner. In the center is a logo reading &amp;ldquo;VisionPort&amp;rdquo;, with the O replaced by a globe with a locator icon in it. Surrounding the logo are animated dots rotating in a circle.&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;resources&#34;&gt;Resources&lt;/h3&gt;
&lt;p&gt;If you&amp;rsquo;re interested in learning more about tkinter, see also:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.python.org/3/library/tkinter.html&#34;&gt;Python tkinter docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/index.html&#34;&gt;The tkinter reference&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Other Python GUI/graphics toolkits you might consider:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.pygame.org/news&#34;&gt;Pygame&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://pyglet.org/&#34;&gt;Pyglet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://doc.qt.io/qtforpython-6/&#34;&gt;PySide6&lt;/a&gt; (official Qt binding)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://riverbankcomputing.com/software/pyqt/intro&#34;&gt;PyQt&lt;/a&gt; (unofficial Qt binding)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://wxpython.org/index.html&#34;&gt;wxPython&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

      </content>
    </entry>
  
    <entry>
      <title>Catching CSS Regressions and Visual Bugs in Continuous Integration</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2021/06/catching-css-regressions-visual-bugs-in-ci/"/>
      <id>https://www.endpointdev.com/blog/2021/06/catching-css-regressions-visual-bugs-in-ci/</id>
      <published>2021-06-24T00:00:00+00:00</published>
      <author>
        <name>Afif Sohaili</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2021/06/catching-css-regressions-visual-bugs-in-ci/banner.jpg&#34; alt=&#34;Blue patterns&#34;&gt;
&lt;a href=&#34;https://unsplash.com/photos/6elNtGvY2S0&#34;&gt;Photo&lt;/a&gt; by &lt;a href=&#34;https://unsplash.com/@macnicolae&#34;&gt;Alexandra Nicolae&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Many web projects nowadays are equipped to simulate real users and automatically test a real use case, but these end-to-end tests still have a weakness. Browser automation tools run tests by performing the interactions and assertions in the context of the DOM of the pages. That is very different from how humans use web applications, which is by looking at the user interface. Due to this, the tests’ definition of a “functional web app” differs from that of a real human.&lt;/p&gt;
&lt;p&gt;For example, let’s take a simple music player with two buttons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;One in green with a play icon, and&lt;/li&gt;
&lt;li&gt;One in red with a stop icon.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The HTML would look something like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&amp;lt;!-- music player --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;button&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;class&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;button is-red&amp;#34;&lt;/span&gt;&amp;gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;class&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;icon icon-stop&amp;#34;&lt;/span&gt;/&amp;gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;button&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;button&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;class&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;button is-green&amp;#34;&lt;/span&gt;&amp;gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;class&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;icon icon-play&amp;#34;&lt;/span&gt;/&amp;gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;button&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In our end-to-end tests, one would typically instruct the test to check:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If the expected classnames exist.&lt;/li&gt;
&lt;li&gt;If clicking the stop button stops the music.&lt;/li&gt;
&lt;li&gt;If clicking the play button plays the music.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If all of the above conditions were met, then the app is considered “functional”. However, the tests have no way to verify if the right colors are actually reflected on the buttons, or if the SVG icons on the button are properly loaded and shown to the users. Unfortunately, for a real user, both of these are very crucial in telling the user how the app functions. If the stylings for the buttons are not implemented or the SVG icons for the buttons fail to load, users will not have a clear indication on how the app works, and the app will &lt;em&gt;not&lt;/em&gt; be considered “functional” by real users, even though the functionalities of both buttons are wired correctly in the code.&lt;/p&gt;
&lt;p&gt;Web developers also frequently deal with styling regressions. This can happen due to the “cascading” characteristic of CSS: Badly scoped CSS values may affect other elements in unrelated areas unintentionally. Many times, styling regressions slip past even the end-to-end tests with browser automation because, again, end-to-end tests only verify that the app’s functionalities are wired together, but do not check if things appear the right way visually.&lt;/p&gt;
&lt;h3 id=&#34;visual-regression-testing-to-the-rescue&#34;&gt;Visual regression testing to the rescue&lt;/h3&gt;
&lt;p&gt;Visual regression testing adds another quality gate in the workflow to allow developers to verify that, after a code change, the user sees what is expected. For example, if the code change is supposed to modify the appearance of an element, developers can verify that it’s doing just that, or at the very least, the code change should not adversely affect other areas. Visual regression testing involves taking screenshots of tested scenarios with changes and comparing them against the baseline (usually screenshots from the stable branch).&lt;/p&gt;
&lt;p&gt;This type of test complements the other testing categories in the test pyramid and ensures user experience is not adversely affected from any given code changes. For more information on the test pyramid and different categories of testing, visit &lt;a href=&#34;/blog/2020/09/automated-testing-with-symfony/&#34;&gt;this blog post&lt;/a&gt; by my colleague, Kevin.&lt;/p&gt;
&lt;p&gt;There are many tools that help with visual regression testing. Here are some tools that you could look into:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;https://percy.io/&#34;&gt;Percy.io&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Generous free tier to get you started.&lt;/li&gt;
&lt;li&gt;5000 free screenshots per month with unlimited team members. This should get you going just fine for smaller projects.&lt;/li&gt;
&lt;li&gt;Integrate with many mainstream end-to-end tests frameworks.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://applitools.com/&#34;&gt;Applitools&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Uses AI-powered computer vision for visual diffing. Said to reduce a lot of false positives and be more efficient.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.testim.io/&#34;&gt;Testim.io&lt;/a&gt; Automation
&lt;ul&gt;
&lt;li&gt;Another service that uses AI-powered computer vision.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://garris.github.io/BackstopJS/&#34;&gt;BackstopJS&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;A Node.js library you can set up yourself.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each tool has its own strengths and weaknesses. For today’s demonstration, we are going to look at implementing visual regression testing with Percy and see how it works.&lt;/p&gt;
&lt;h3 id=&#34;integrating-percy&#34;&gt;Integrating Percy&lt;/h3&gt;
&lt;p&gt;Percy provides comprehensive guides to get you started with many popular end-to-end test frameworks such as Selenium, Cypress, TestCafe, and Capybara. You can check the documentation to see how to integrate it with your project. Since we work with Rails a lot at End Point, we are going to demonstrate Percy within a Rails project alongside Rspec/​Capybara-driven end-to-end tests.&lt;/p&gt;
&lt;p&gt;In this article, we are going to use this &lt;a href=&#34;https://github.com/afifsohaili-ep/dockified-demo&#34;&gt;simple Rails project&lt;/a&gt; I built for fun as the demo. It is just a simple wiki project using Vue and Rails. Let’s start.&lt;/p&gt;
&lt;h4 id=&#34;1-register-an-account-at-percyio&#34;&gt;1. Register an account at percy.io&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;Head to &lt;a href=&#34;https://percy.io&#34;&gt;percy.io&lt;/a&gt; and click the “Start for Free” button on the top-right side.&lt;/li&gt;
&lt;li&gt;Choose the preferred sign up method. In this case, we are just going to use email and password, so we will choose &lt;em&gt;Sign Up with Browserstack&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Fill in the email and password and complete the registration. It will then send an email to ask us to verify our email address. Let’s check our email and complete the email verification.&lt;/li&gt;
&lt;li&gt;Next, we will see the Get Started screen. We will choose &lt;em&gt;Create a New Project&lt;/em&gt; and give it a relevant name.&lt;/li&gt;
&lt;li&gt;We have successfully created a new percy.io project and should be on the project page. There are some links to setup guides that can help us integrate Percy into our project.
&lt;img src=&#34;/blog/2021/06/catching-css-regressions-visual-bugs-in-ci/first-setup.png&#34; alt=&#34;Our project page&#34;&gt;&lt;/li&gt;
&lt;li&gt;Let’s navigate to &lt;em&gt;Project Settings&lt;/em&gt;. We will scroll down to the &lt;em&gt;Branch Settings&lt;/em&gt; section and make sure the &lt;em&gt;Default base branch&lt;/em&gt; points to the primary branch of our project’s repository. This is usually &lt;code&gt;main&lt;/code&gt;, &lt;code&gt;master&lt;/code&gt; or &lt;code&gt;develop&lt;/code&gt; depending on your team’s workflow. You might want to make the same branch auto-approved as well. That way, screenshots from this branch will be treated as the baseline and will be used when comparing against the development branches. In our case, we are using the &lt;code&gt;main&lt;/code&gt; branch so we will specify that in both fields.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Great, we have now successfully set up Percy. There are other configurations in the Project Settings page (e.g. browsers to enable, diff sensitivity, and whether or not to allow public viewers), but we do not have to care about them for the purpose of this demo. You can always come back and tweak this later according to your project’s requirements.&lt;/p&gt;
&lt;h4 id=&#34;2-integrate-with-the-rails-project&#34;&gt;2. Integrate with the Rails project&lt;/h4&gt;
&lt;p&gt;Now, let’s look at how to integrate Percy with our demo Rails project. Percy provides a comprehensive guide to do that &lt;a href=&#34;https://docs.percy.io/docs/capybara&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;1. First, let’s export the &lt;code&gt;PERCY_TOKEN&lt;/code&gt; provided to us in the &lt;em&gt;Builds&lt;/em&gt; page into our environment variable.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#038&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;PERCY_TOKEN&lt;/span&gt;=&amp;lt;value&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;2. Then, we will add &lt;code&gt;percy-capybara&lt;/code&gt; into our Gemfile and run &lt;code&gt;bundle install&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;gem &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;percy-capybara&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;3. Next, let’s install &lt;code&gt;@percy/agent&lt;/code&gt; via npm/​Yarn.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;yarn add --dev @percy/agent&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That’s it! Now let’s look at adding Percy to our feature specs.&lt;/p&gt;
&lt;h4 id=&#34;3-percy-in-feature-specs&#34;&gt;3. Percy in Feature Specs&lt;/h4&gt;
&lt;p&gt;Percy has to be integrated into feature specs (i.e. end-to-end tests) because it requires the app to be running in order for it to be able to take screenshots. Hence, it will not work in unit tests where the application context is mocked and chunks of code are tested in isolation.&lt;/p&gt;
&lt;p&gt;First, we will check out the primary working branch &lt;code&gt;main&lt;/code&gt; and take a look at the existing feature specs:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# spec/integration/document_spec.rb&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#038&#34;&gt;require&lt;/span&gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;rails_helper&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;RSpec&lt;/span&gt;.feature &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Add documents&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;js&lt;/span&gt;: &lt;span style=&#34;color:#080&#34;&gt;true&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  before &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:each&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;User&lt;/span&gt;.create(&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;email&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;user@example.com&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;password&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;password&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    visit &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;/documents/new&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    within(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;#new_user&amp;#34;&lt;/span&gt;) &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      fill_in &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Email&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;with&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;user@example.com&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      fill_in &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Password&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;with&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;password&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    click_button &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Log in&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  scenario &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;User visits page to create new document&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    expect(page).to have_text(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Create new document&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  scenario &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;User sees validation errors&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    click_button &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Create&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    expect(page).to have_text(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Title can&amp;#39;t be blank&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    expect(page).to have_text(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Body can&amp;#39;t be blank&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    expect(page).to have_text(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Body is too short (minimum is 10 characters)&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The feature specs are pretty simple. &lt;code&gt;/documents/new&lt;/code&gt; is a route that only registered users can access, so upon visiting the path, &lt;code&gt;devise&lt;/code&gt; is going to ask the visitor to authenticate before they can access the page.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The first test case verifies that the user can see the &lt;code&gt;Create new document&lt;/code&gt; page upon successfully signing in.&lt;/li&gt;
&lt;li&gt;The second case verifies that the user will see validation errors if they do not fill in the required details for the new document.&lt;/li&gt;
&lt;/ol&gt;
&lt;h5 id=&#34;adding-percy-into-the-mix&#34;&gt;Adding Percy into the mix&lt;/h5&gt;
&lt;p&gt;To add Percy, simply &lt;code&gt;require &amp;quot;percy&amp;quot;&lt;/code&gt; at the top of the spec file and add &lt;code&gt;Percy.snapshot(page, { name: &#39;&amp;lt;screenshot description&amp;gt;&#39; })&lt;/code&gt; on the lines in which we want Percy to capture.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# spec/integration/document_spec.rb&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#038&#34;&gt;require&lt;/span&gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;rails_helper&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# Include Percy in our feature specs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#038&#34;&gt;require&lt;/span&gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;percy&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;RSpec&lt;/span&gt;.feature &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Add documents&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;js&lt;/span&gt;: &lt;span style=&#34;color:#080&#34;&gt;true&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  before &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:each&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;User&lt;/span&gt;.create(&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;email&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;user@example.com&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;password&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;password&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    visit &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;/documents/new&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    within(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;#new_user&amp;#34;&lt;/span&gt;) &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      fill_in &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Email&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;with&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;user@example.com&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      fill_in &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Password&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;with&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;password&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    click_button &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Log in&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  scenario &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;User visits page to create new document&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    expect(page).to have_text(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Create new document&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Screenshot when we&amp;#39;re redirected to the create new document page&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Percy&lt;/span&gt;.snapshot(page, { &lt;span style=&#34;color:#038&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Create document page&amp;#39;&lt;/span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  scenario &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;User sees validation errors&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    click_button &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Create&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    expect(page).to have_text(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Title can&amp;#39;t be blank&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    expect(page).to have_text(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Body can&amp;#39;t be blank&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    expect(page).to have_text(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Body is too short (minimum is 10 characters)&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Screenshot when we see the errors&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Percy&lt;/span&gt;.snapshot(page, { &lt;span style=&#34;color:#038&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Create document page validation error&amp;#39;&lt;/span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That’s it! Easy enough, right? Next, let’s run the tests so that the screenshots are taken and sent to percy.io. To do this, we cannot run the usual &lt;code&gt;bundle exec rspec&lt;/code&gt;. Instead, we will have to run Percy and pass &lt;code&gt;rspec&lt;/code&gt; to it.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ yarn percy &lt;span style=&#34;color:#038&#34;&gt;exec&lt;/span&gt; -- rspec&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: This is the command that you should use if you have continuous integration set up.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;And we’re done here! Now, if we go to percy.io, we can see that a new build has been created for us. After a few minutes, we should see the screenshots of the app.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2021/06/catching-css-regressions-visual-bugs-in-ci/first-build.png&#34; alt=&#34;Our first build on Percy&#34;&gt;&lt;/p&gt;
&lt;h4 id=&#34;4-introduce-visual-changes-and-see-how-percyio-compares&#34;&gt;4. Introduce visual changes and see how percy.io compares&lt;/h4&gt;
&lt;p&gt;The last step we ran was on the primary working branch. Since we set this branch as the baseline and that the screenshots from this branch would be auto-approved, we are not going to be asked to verify anything on this build.&lt;/p&gt;
&lt;p&gt;Therefore, to demonstrate the visual diff-ing feature of Percy, let’s create another branch &lt;code&gt;highlight-create-button&lt;/code&gt;, and let’s make the &lt;code&gt;Create Document&lt;/code&gt; button appear bigger as well as change the text to just say &lt;code&gt;Create&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;%# app/views/documents/_form.html.erb %&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;lt;%#&lt;/span&gt; ... &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;%&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;lt;%# Old button %&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;%#= form.submit class: &amp;#39;button is-primary&amp;#39; %&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;lt;%#&lt;/span&gt; &lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;New&lt;/span&gt; button &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;%&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;lt;%= form.submit &amp;#34;Create&amp;#34;, class: &amp;#39;button is-primary is-large&amp;#39; %&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Awesome! Now, let’s rerun &lt;code&gt;yarn percy exec -- rspec&lt;/code&gt; and analyse the visual diff on percy.io.&lt;/p&gt;
&lt;p&gt;Once percy.io is done processing the new screenshots, it will compare the screenshots against the baseline in the &lt;code&gt;main&lt;/code&gt; branch. We will see this in our new build:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2021/06/catching-css-regressions-visual-bugs-in-ci/visual-diff-sample.png&#34; alt=&#34;Visual diff on Percy&#34;&gt;&lt;/p&gt;
&lt;p&gt;As you can see, Percy highlights any changed areas in red. This makes it easier for us to spot the differences. In this case, it is expected for the button to change, so we can just go ahead and approve the build with the green button at the top.&lt;/p&gt;
&lt;h4 id=&#34;5-what-about-style-regressions&#34;&gt;5. What about style regressions?&lt;/h4&gt;
&lt;p&gt;Let’s do one more demonstration. This time, let’s simulate a CSS change gone wrong:&lt;/p&gt;
&lt;p&gt;Create another git branch called &lt;code&gt;css-regression&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Add a &lt;code&gt;text-indent: -5rem&lt;/code&gt; to the error messages to push the text out of its container. This is what it would look like:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2021/06/catching-css-regressions-visual-bugs-in-ci/css-regression.png&#34; alt=&#34;Regression&#34;&gt;&lt;/p&gt;
&lt;p&gt;Let’s run &lt;code&gt;bundle exec rspec&lt;/code&gt;. We will get this output:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;gt; bundle exec rspec
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Capybara starting Puma...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;* Version 5.3.2 , codename: Sweetnighter
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;* Min threads: 0, max threads: 4
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;* Listening on http://127.0.0.1:65318
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;..
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Finished in 2.62 seconds (files took 1.3 seconds to load)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;2 examples, 0 failures&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As you can see, the tests were not able to catch this styling regression. This is because the error messages specified in the assertion still exist on the page, so the use case is not considered broken in the “eyes” of the regular feature specs.&lt;/p&gt;
&lt;p&gt;Now, run &lt;code&gt;yarn percy exec -- rspec&lt;/code&gt; to send the screenshots to percy.io to be processed.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2021/06/catching-css-regressions-visual-bugs-in-ci/percy-regression.png&#34; alt=&#34;Regression on Percy&#34;&gt;&lt;/p&gt;
&lt;p&gt;Great, percy.io was able to catch the regression here in the visual diff! A reviewer can just mark the build as “Requested changes” and leave a comment in there to have it fixed.&lt;/p&gt;
&lt;h4 id=&#34;6-responsive-design-and-cross-browser-testing&#34;&gt;6. Responsive design and cross-browser testing&lt;/h4&gt;
&lt;p&gt;Percy can also help test your app’s UI on different browsers — namely Edge, Firefox, and Chrome. By default, all three browsers are enabled. You do not need to do anything else.&lt;/p&gt;
&lt;p&gt;It can be used to test against different screen sizes as well. You can just select the screen sizes that are relevant to you and instruct Percy to take screenshots at those screen sizes automatically. This is particularly helpful as testing on several screen size variants can be very time-consuming. With Percy, you do not have to pay attention to the screenshot variants that do not diverge from the baseline.&lt;/p&gt;
&lt;p&gt;In order to specify screen sizes, just pass a list of screen sizes that you are interested in, like so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Percy&lt;/span&gt;.snapshot(page, { &lt;span style=&#34;color:#038&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Create document page&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;widths&lt;/span&gt;: [&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;480&lt;/span&gt;, &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;768&lt;/span&gt;, &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1024&lt;/span&gt;, &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1366&lt;/span&gt;] })&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Rerun &lt;code&gt;yarn percy exec -- rspec&lt;/code&gt;. We can now see the different variants (browsers and screen sizes) of each scenario on the top-right corner of the screen.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2021/06/catching-css-regressions-visual-bugs-in-ci/variants.png&#34; alt=&#34;Variants&#34;&gt;&lt;/p&gt;
&lt;p&gt;That’s it! We have successfully demonstrated how Percy can help us verify expected UI changes and catch visual regressions.&lt;/p&gt;
&lt;h3 id=&#34;caveats&#34;&gt;Caveats&lt;/h3&gt;
&lt;p&gt;As helpful and as crucial as it is, visual regression testing is not without its caveats. One of the most common issues with visual regression testing is, in some cases, it can produce a huge amount of false positives. Major-but-intentional layout shifts and changes to shared elements may result in having to review a huge amount of screenshots in the project.&lt;/p&gt;
&lt;p&gt;For example, let’s say you increase the height of the site header (which is a common element in &lt;em&gt;all&lt;/em&gt; pages) by 30px. When Percy processes the screenshots, it will invalidate most of the screenshots in the project, because elements would have been shifted down by 30px as a result of the taller header. For large projects, reviewing each screenshot one-by-one can be a hassle, especially with the tens of permutations for each screenshot to cater different screen sizes and browsers. As a result, one would be tempted to just click “Approve build”, thinking all changed areas would just be elements being pushed down, but that may not necessarily be true for elements with &lt;code&gt;position: absolute;&lt;/code&gt;; they might get hidden behind the now taller header, and this may cause visual regressions to still slip into production, which kind of defeats visual testing’s original purpose.&lt;/p&gt;
&lt;p&gt;Other than that, dealing with external resources can also result in flaky builds (e.g. if a page has an iframe or links to an external image). There are a lot of ways in which an external resource can change, e.g. image quality, content, updated resource, etc., and because these resources are outside of our control, they may become a source of false positives and a nuisance to the reviewers.&lt;/p&gt;
&lt;p&gt;One way to deal with this is to not screenshot whole pages. Instead, screenshot only the individual components that are relevant to each scenario. This way, layout shifts in other parts of the pages will not affect the build in major ways, and it also helps cut out the changes from external resources on the page. Unfortunately, Percy does not have the capability to do this &lt;a href=&#34;https://github.com/percy/percy-cypress/issues/56#issuecomment-824183541&#34;&gt;yet&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;So, there you go! I hope this article has given you a good introduction to visual regression testing, why should you have it, and how to get started with it. Hope it will benefit you and your team in pushing great web applications!&lt;/p&gt;
&lt;h3 id=&#34;other-resources&#34;&gt;Other resources&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=spyKZ-p3UgE&#34;&gt;(YouTube) Your Tests Lack Vision: Adding Eyes to your Automation Framework&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=_ls5P97-REU&#34;&gt;(YouTube) Visual Regression Testing for Your Web Apps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://techblog.commercetools.com/keeping-a-react-design-system-consistent-f055160d5166&#34;&gt;Keeping a React Design System consistent: using visual regression testing to save time and headaches&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@philgourley/making-visual-regression-useful-acfae27e5031&#34;&gt;Making visual regression useful&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

      </content>
    </entry>
  
    <entry>
      <title>Campendium: A Responsive, Fancy Detail Page</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2019/09/campendium-detail-page/"/>
      <id>https://www.endpointdev.com/blog/2019/09/campendium-detail-page/</id>
      <published>2019-09-09T00:00:00+00:00</published>
      <author>
        <name>Steph Skardal</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2019/09/campendium-detail-page/banner.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;This week, I was very excited to deploy a project for &lt;a href=&#34;https://www.campendium.com/&#34;&gt;Campendium&lt;/a&gt;, one of our long-time clients. As noted in &lt;a href=&#34;/blog/2019/08/campendium-updates/&#34;&gt;my recent post on Campendium updates for the year&lt;/a&gt;, Campendium has thousands of listings of places to camp and provides a great infrastructure for the development of a rich community of travelers around North America.&lt;/p&gt;
&lt;p&gt;For the past few months, I’ve been working on a significant update to Campendium’s campground detail page, the page template where in-depth information is provided for each of Campendium’s locations. This is equivalent to a product detail page on an ecommerce site.&lt;/p&gt;
&lt;p&gt;The “guts” of the update included a new detail page design with expanded responsiveness, the introduction of 360° videos, and expanded user driven content in the form of Question &amp;amp; Answer (Q&amp;amp;A or QnA), reviews, notes, nightly rates, etc. Read on to find out more and see video examples of several of the features!&lt;/p&gt;
&lt;h3 id=&#34;user-interface--responsiveness-updates&#34;&gt;User Interface &amp;amp; Responsiveness Updates&lt;/h3&gt;
&lt;p&gt;One of the things the Campendium team and I are most proud of here is the responsiveness of the new design. In the case of traveling and camping, responsiveness is important since a large amount of traffic comes from mobile devices, relative to what you might see in other industries.&lt;/p&gt;
&lt;p&gt;User images are shown as “hero images” and the user interface updates depending on the browser device and width, as shown in the following videos for &lt;a href=&#34;https://www.campendium.com/gilbert-ray-campground&#34;&gt;Gilbert Ray Campground&lt;/a&gt; and &lt;a href=&#34;https://www.campendium.com/cayuga-lake-state-park&#34;&gt;Cayuga Lake State Park&lt;/a&gt;.&lt;/p&gt;
&lt;p style=&#34;text-align:center;font-weight:bold;&#34;&gt;&lt;iframe src=&#34;https://drive.google.com/file/d/1FFF8GcztnV-KGaJ4pjDYixCbkxw6kduu/preview&#34; width=&#34;770&#34; height=&#34;450&#34;&gt;&lt;/iframe&gt;A preview of responsive behavior on the campground detail page with user submitted photos.&lt;/p&gt;
&lt;p style=&#34;text-align:center;font-weight:bold;&#34;&gt;&lt;iframe src=&#34;https://drive.google.com/file/d/1caQZT9ogh_piuTX2UDIvWGEBZse0zwnx/preview&#34; width=&#34;770&#34; height=&#34;450&#34;&gt;&lt;/iframe&gt;A second preview of responsive behavior on the campground detail page with embedded maps.&lt;/p&gt;
&lt;p&gt;Another updated design usability tweak was a sticky navigation bar to navigation throughout the page, which can get especially long with user submitted content. See how the “Overview”, “Video”, etc. links become sticky as you scroll down on the page, and the current region coming into view of the page is underlined:&lt;/p&gt;
&lt;p style=&#34;text-align:center;font-weight:bold;&#34;&gt;&lt;iframe src=&#34;https://drive.google.com/file/d/1w_MjP_HUXntYXHTG6FeREtMWwhKv4KgG/preview&#34; width=&#34;770&#34; height=&#34;450&#34;&gt;&lt;/iframe&gt;Navigation becomes fixed to the top of the browser as a user scrolls through the content.&lt;/p&gt;
&lt;p&gt;Campendium uses Ruby on Rails as a backend, a bit of &lt;a href=&#34;https://getbootstrap.com/&#34;&gt;Bootstrap&lt;/a&gt;, and &lt;a href=&#34;https://sass-lang.com/&#34;&gt;Sass&lt;/a&gt; and best practice responsive design is used throughout this updated user interface.&lt;/p&gt;
&lt;h3 id=&#34;360-videos&#34;&gt;360° Videos&lt;/h3&gt;
&lt;p&gt;The Campendium team has been hard at work creating 360 degree videos of the various locations to provide significant value to their users. These videos are now embedded into the campground detail page. Video hosting is provided by YouTube and dropped into the Campendium campground HTML template.&lt;/p&gt;
&lt;p style=&#34;text-align:center;font-weight:bold;&#34;&gt;&lt;iframe src=&#34;https://drive.google.com/file/d/12XEu-95diRRMjlqb28jD0xdj9qIWXSb7/preview&#34; width=&#34;770&#34; height=&#34;450&#34;&gt;&lt;/iframe&gt;A video in a video—a 360 degree video example.&lt;/p&gt;
&lt;p&gt;I personally think these 360° videos are awesome, especially to those visual learners out there! You can learn so much from a video that can be hard to capture in user reviews.&lt;/p&gt;
&lt;h3 id=&#34;community-qa&#34;&gt;Community Q&amp;amp;A&lt;/h3&gt;
&lt;p&gt;Another new feature with this deploy is the introduction of community driven Question &amp;amp; Answer. Users can submit questions about a campground, and users who have contributed to this campground are invited to respond (or they can opt-out site-wide to responding to questions). In addition to Q&amp;amp;A itself, user content can be “upvoted” (marked as Helpful) or flagged for an improved user content browsing experience.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2019/09/campendium-detail-page/community.png&#34; alt=&#34;Campendium community in Question &amp;amp; Answer&#34;&gt;&lt;/p&gt;
&lt;p&gt;All Q&amp;amp;A functionality is driven by JavaScript coupled with the Ruby on Rails backend.&lt;/p&gt;
&lt;h3 id=&#34;expansion-of-user-contributed-content-and-features&#34;&gt;Expansion of User Contributed Content and Features&lt;/h3&gt;
&lt;p&gt;Finally, this update includes expansion of user contributed content in addition to Q&amp;amp;A. This includes the ability for users to contribute notes, cell signal reports, and nightly rates. Reviews can now be filtered by user profile information and sorted by date or specific ranking. Reviews can also be searched by keywords and those keywords highlighted in the results. The following video demonstrates sorting and searching of reviews with highlighted keyword match:&lt;/p&gt;
&lt;p style=&#34;text-align:center;font-weight:bold;&#34;&gt;&lt;iframe src=&#34;https://drive.google.com/file/d/1G2KGfxCkOmuJm-AJE8BPXUC-OWdw1iQS/preview&#34; width=&#34;770&#34; height=&#34;450&#34;&gt;&lt;/iframe&gt;Review searchability with highlighting and filterability.&lt;/p&gt;
&lt;p&gt;All search functionality driven by JavaScript on the frontend, and a Ruby on Rails backend coupled with &lt;a href=&#34;https://github.com/sunspot/sunspot&#34;&gt;Sunspot&lt;/a&gt; and &lt;a href=&#34;https://lucene.apache.org/solr/&#34;&gt;Solr&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;who-doesnt-love-stats&#34;&gt;Who Doesn’t Love Stats?&lt;/h3&gt;
&lt;p&gt;Just for the sake of sharing stats, from GitHub, this update included:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;215 changed files&lt;/li&gt;
&lt;li&gt;6,284 additions&lt;/li&gt;
&lt;li&gt;3,924 deletions (removal / cleanup of unneeded JavaScript files)&lt;/li&gt;
&lt;/ul&gt;

      </content>
    </entry>
  
    <entry>
      <title>Campendium v2019: A Summary of Recent Updates</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2019/08/campendium-updates/"/>
      <id>https://www.endpointdev.com/blog/2019/08/campendium-updates/</id>
      <published>2019-08-05T00:00:00+00:00</published>
      <author>
        <name>Steph Skardal</name>
      </author>
      <content type="html">
        &lt;p&gt;This year has brought a handful of exciting changes for &lt;a href=&#34;https://www.campendium.com/&#34;&gt;Campendium&lt;/a&gt;, one of End Point’s long-time clients, by yours truly. Created by campers for campers, Campendium has thousands of listings of places to camp, from swanky RV parks to free remote destinations, vetted by a team of full-time travelers and reviewed by over 200,000 members. I thought I would take some time to summarize these recent updates.&lt;/p&gt;
&lt;h3 id=&#34;maps-and-clustering&#34;&gt;Maps and Clustering&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2019/08/campendium-updates/map-clusters.png&#34; alt=&#34;Campendium map clustering of campground locations&#34;&gt;&lt;/p&gt;
&lt;p&gt;Campendium uses &lt;a href=&#34;https://www.mapbox.com/&#34;&gt;Mapbox&lt;/a&gt; for map rendering to display campgrounds and locations throughout North America. One of the new features added this year was &lt;a href=&#34;https://docs.mapbox.com/mapbox-gl-js/example/cluster/&#34;&gt;clustering&lt;/a&gt; of campground locations, where campgrounds are grouped together and presented in a “cluster” with a size relative to how many campgrounds are in the cluster.&lt;/p&gt;
&lt;p&gt;If a user is searching for campgrounds in a broad location, they can see where campgrounds might be more densely grouped by location. Once a user zooms in zoom in a couple of clicks, the campgrounds are no longer clustered and individual campgrounds locations can be seen. While working on this update, we spent a good amount of our time tweaking and troubleshooting the optimal clustering behavior to provide the most benefit to those searching for a campground. &lt;a href=&#34;https://docs.mapbox.com/mapbox-gl-js/api/&#34;&gt;Mapbox GL JS&lt;/a&gt; works in parallel with &lt;a href=&#34;https://reactjs.org/&#34;&gt;ReactJS&lt;/a&gt;, and runs with a Ruby on Rails back-end.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2019/08/campendium-updates/map-non-clusters.jpg&#34; alt=&#34;Campendium map non-clustering of campground locations after zooming in&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;advanced-filtering&#34;&gt;Advanced Filtering&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2019/08/campendium-updates/map-filtering.jpg&#34; alt=&#34;Campendium advanced filtering&#34;&gt;&lt;/p&gt;
&lt;p&gt;Another exciting was the introduction of advanced filtering in the search interface, presented in combination with map display. Users can filter campgrounds by category (e.g. Public Land, RV Parking, Parking, Dump Station), filter by price (with a slider), hookups, campground policy (e.g. age or pet restrictions), discounts, recreation, and facilities. All of this search filtering is driven by &lt;a href=&#34;https://github.com/sunspot/sunspot&#34;&gt;Sunspot&lt;/a&gt;, a Ruby on Rails gem for working with the popular &lt;a href=&#34;https://lucene.apache.org/solr/&#34;&gt;Solr&lt;/a&gt; search engine. Results can be sorted by user provided reviews, price or distance from a specific GPS location. Here, much care was given to provide the best user interface for presenting this valuable functionality.&lt;/p&gt;
&lt;h3 id=&#34;supporters-only-features&#34;&gt;“Supporters Only” Features&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2019/08/campendium-updates/supporters.jpg&#34; alt=&#34;Campendium Supporters only, Subscriptions&#34;&gt;&lt;/p&gt;
&lt;p&gt;Another recent update to Campendium includes functionality to offer user subscriptions. Registered users can sign up to support Campendium on a monthly or annual basis, and subscriptions are set to auto-renew at the end of their subscription period. This paid support hides advertisements throughout the site (advertisements are controlled by a third party), and advanced filtering on cell reception. There are plans to expand supporter features in the future. Ruby on Rails combined with &lt;a href=&#34;https://stripe.com/docs/stripe-js/reference&#34;&gt;StripeJS&lt;/a&gt; is used to manage subscription payments, and Ruby on Rails also serves as a backend for &lt;a href=&#34;https://developer.apple.com/in-app-purchase/&#34;&gt;In-App Purchases&lt;/a&gt; of subscriptions from the App store.&lt;/p&gt;
&lt;h3 id=&#34;always-responsive-and-latency-aware&#34;&gt;Always Responsive and Latency-Aware&lt;/h3&gt;
&lt;p&gt;Because a large portion of the Campendium visitors are on the road, it’s important to have both a responsive design and to build for bandwidth limitations for users. Throughout the development of these new features, responsive and mobile friendly designs were implemented leveraging &lt;a href=&#34;https://sass-lang.com/&#34;&gt;Sass&lt;/a&gt;, sometimes requiring help from the rest of the knowledgeable &lt;a href=&#34;/team/&#34;&gt;End Point team&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Many of the pages throughout the site are fully cached including the homepage, search result pages, and campground detail page, and cookies are used to indicate user status. In some cases, user submitted campground images are lazy-loaded to mitigate bandwidth limitations.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2019/08/campendium-updates/mobile.jpg&#34; alt=&#34;Mobile, responsive design for Campendium&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;whats-next&#34;&gt;What’s Next?&lt;/h3&gt;
&lt;p&gt;While I didn’t go into much technical depth on these updates, I am happy that the updates represent a broad spectrum of full-stack development skills featuring nginx, Ruby on Rails, 3rd party integration including StripeJS, MapboxGL and IAP (Apple), a JavaScript framework with ReactJS, and working with Ruby gems to leverage other tools, for example, Solr (Sunspot) and Sass.&lt;/p&gt;
&lt;p&gt;In the future, Campendium plans to continue using these tools to see a more interactive, social campground detail page, and has plans to expand outside of North America. You can visit Campendium &lt;a href=&#34;https://www.campendium.com/&#34;&gt;here&lt;/a&gt;, or find them on Instagram &lt;a href=&#34;https://www.instagram.com/campendium/?hl=en&#34;&gt;here&lt;/a&gt; to follow their exciting announcements!&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Hue’s on First: How we used responsive bulbs to join software and hardware for a busy medical practice</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2016/03/hues-on-first/"/>
      <id>https://www.endpointdev.com/blog/2016/03/hues-on-first/</id>
      <published>2016-03-14T00:00:00+00:00</published>
      <author>
        <name>Liz Flyntz</name>
      </author>
      <content type="html">
        &lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2016/03/hues-on-first/FastTrackoffice.jpg&#34;/&gt;&lt;/div&gt;
&lt;p&gt;In 2014 we began working with a busy bariatric surgery office in Long Island to create a system that would allow the practice to better manage doctor paging and patient wait time. By placing a responsive, color-coded light bulb and tablet outside each examination room, the staff could see which rooms were empty, which were occupied by a patient waiting on a specific doctor, and in which a doctor-patient consultation was in process. Outside each room is a tablet with information including the patient number, the attending doctor’s name, and the wait time.&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2016/03/hues-on-first/FastTrackmapmonitor.jpg&#34;/&gt;&lt;/div&gt;
&lt;p&gt;In addition to providing a comprehensive, granular paging service for doctors, Fast Track also provides feedback to the practice. This feedback includes average patient wait times per doctor, per time of day, and per procedure. This allows the practice to make necessary changes and increase patient satisfaction and peace of mind.&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2016/03/hues-on-first/FastTrackapp.jpg&#34;/&gt;&lt;/div&gt;
&lt;p&gt;I asked Danny Divita, one of the main developers on this project, to tell us more about the &lt;a href=&#34;http://www2.meethue.com/en-us/&#34;&gt;Hue&lt;/a&gt;/ &lt;a href=&#34;https://web.archive.org/web/20180107115334/http://www.fasttrackmed.com/&#34;&gt;FastTrack&lt;/a&gt; interface.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;LF: Describe the project for which we used Hue bulbs. What were all the pieces that needed fitting together?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;DD: The Hue bulbs are being used for a bariatric clinic to alert the staff when pages are sent to of pages to specific rooms. Alongside the lights the API also has to work with a RF device-locating API. The RF API had to be coordinated with the Hue light API to coordinate changing colors, alerts, and states.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;LF: Why were Hue bulbs a good solution for the problem of creating an easy-to-understand, integratable visual alert system? Did you consider any alternatives?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;DD: From a cost perspective and ease of integration the bulbs were a good solution for this project. There are other solutions, but they are more costly and the integration would have taken longer.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;LF: How did you hear about Hue bulbs? Was the API easy to access and work with? How was using Hue’s testing environment?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;DD: The client suggested the bulbs during their research before having us develop their application. The API is extremely easy to work with and well documented. The Hue API has a built-in testing feature that made it easy to understand the API and develop around it.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;LF: What was the most difficult thing about integrating this technology? Can you think of any other commercial implementations of these bulbs? Specifically in the medical industry?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;DD: The most difficult aspect was trying to come up with a good alert (blinking) feature. The API has some built in alerts, but they are limited to a set timed interval. We had to develop around this to ensure the alerts stayed constant and did not time out. Another application for these lights in the medical field could be to indicate severity of a situation or condition. Because the lights can change color, the applications can signal the proper response for the staff.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;LF: Did you write any new software for this project? Is that work available to other developers?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;DD: We used a third party library that helps to manage stateless workflows. Our application expanded upon this library to design a workflow engine that manages paging the staff, Hue lights, and RF device locating.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Medium-inspired Parallax Blur Effect For WordPress</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2016/01/medium-inspired-parallax-blur-effect/"/>
      <id>https://www.endpointdev.com/blog/2016/01/medium-inspired-parallax-blur-effect/</id>
      <published>2016-01-22T00:00:00+00:00</published>
      <author>
        <name>Marina Lohova</name>
      </author>
      <content type="html">
        &lt;p&gt;Are you are running a WordPress blog, but secretly dying to have that &lt;a href=&#34;https://medium.com/&#34;&gt;Medium&lt;/a&gt; parallax blur effect? I recently implemented this, and would like to share it with you. By the way, while I was working on the article, the effect was removed from Medium, which only makes having one on the website more precious.&lt;/p&gt;
&lt;p&gt;Let’s assume that we have our custom theme class &lt;code&gt;MyTheme&lt;/code&gt;. In functions.php:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;MyThemeBaseFunctions&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#06b;font-weight:bold&#34;&gt;__construct&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    add_image_size(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;blurred&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1600&lt;/span&gt;, &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1280&lt;/span&gt;, &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    add_filter(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;wp_generate_attachment_metadata&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;array&lt;/span&gt;(&lt;span style=&#34;color:#369&#34;&gt;$this&lt;/span&gt;,&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;wp_blur_attachment_filter&amp;#39;&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We added a custom image size blurred, and a callback &lt;code&gt;wp_blur_attachment_filter&lt;/code&gt; to &lt;code&gt;wp_generate_attachment_metadata&lt;/code&gt;. Here’s where the magic happens.&lt;/p&gt;
&lt;p&gt;Before that let’s talk a little about ImageMagick, a powerful library for image processing that we will use to create the blurred effect. After some experimenting I figured that the image needed to be darkened, and then a regular blur should be applied with sigma=20. You can read more about these settings at &lt;a href=&#34;http://www.imagemagick.org/Usage/blur/#blur&#34;&gt;ImageMagick Blur Usage&lt;/a&gt;. I used Gaussian Blur at first, but found the processing was extremely slow, and there wasn’t much difference in the end result compared to other blur methods.&lt;/p&gt;
&lt;p&gt;Now we are ready to write a blurring function:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#06b;font-weight:bold&#34;&gt;wp_blur_attachment_filter&lt;/span&gt;(&lt;span style=&#34;color:#369&#34;&gt;$image_data&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; ( !isset(&lt;span style=&#34;color:#369&#34;&gt;$image_data&lt;/span&gt;[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;sizes&amp;#39;&lt;/span&gt;][&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;blurred&amp;#39;&lt;/span&gt;]) )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$image_data&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#369&#34;&gt;$upload_dir&lt;/span&gt; = wp_upload_dir();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#369&#34;&gt;$src&lt;/span&gt; = &lt;span style=&#34;color:#369&#34;&gt;$upload_dir&lt;/span&gt;[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;path&amp;#39;&lt;/span&gt;] . &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;/&amp;#39;&lt;/span&gt; . &lt;span style=&#34;color:#369&#34;&gt;$image_data&lt;/span&gt;[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;sizes&amp;#39;&lt;/span&gt;][&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;large&amp;#39;&lt;/span&gt;][&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;file&amp;#39;&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#369&#34;&gt;$destination&lt;/span&gt; = &lt;span style=&#34;color:#369&#34;&gt;$upload_dir&lt;/span&gt;[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;path&amp;#39;&lt;/span&gt;] . &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;/&amp;#39;&lt;/span&gt; . &lt;span style=&#34;color:#369&#34;&gt;$image_data&lt;/span&gt;[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;sizes&amp;#39;&lt;/span&gt;][&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;blurred&amp;#39;&lt;/span&gt;][&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;file&amp;#39;&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#369&#34;&gt;$imagick&lt;/span&gt; = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; \Imagick(&lt;span style=&#34;color:#369&#34;&gt;$src&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#369&#34;&gt;$imagick&lt;/span&gt;-&amp;gt;&lt;span style=&#34;color:#369&#34;&gt;blurImage&lt;/span&gt;(&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;20&lt;/span&gt;, Imagick::&lt;span style=&#34;color:#369&#34;&gt;CHANNEL_ALL&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#369&#34;&gt;$imagick&lt;/span&gt;-&amp;gt;&lt;span style=&#34;color:#369&#34;&gt;modulateImage&lt;/span&gt;(&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;75&lt;/span&gt;, &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;105&lt;/span&gt;, &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;100&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#369&#34;&gt;$imagick&lt;/span&gt;-&amp;gt;&lt;span style=&#34;color:#369&#34;&gt;writeImage&lt;/span&gt;(&lt;span style=&#34;color:#369&#34;&gt;$destination&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$image_data&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I darken the image:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;$imagick&lt;/span&gt;-&amp;gt;&lt;span style=&#34;color:#369&#34;&gt;modulateImage&lt;/span&gt;(&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;75&lt;/span&gt;, &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;105&lt;/span&gt;, &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;100&lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And I blur the image:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;$imagick&lt;/span&gt;-&amp;gt;&lt;span style=&#34;color:#369&#34;&gt;blurImage&lt;/span&gt;(&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;20&lt;/span&gt;, Imagick::&lt;span style=&#34;color:#369&#34;&gt;CHANNEL_ALL&lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now we are able to use the custom image size in the template. Place the helper function in functions.php:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#06b;font-weight:bold&#34;&gt;non_blurred&lt;/span&gt;(&lt;span style=&#34;color:#369&#34;&gt;$src&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#369&#34;&gt;$url&lt;/span&gt; = get_site_url() . substr(&lt;span style=&#34;color:#369&#34;&gt;$src&lt;/span&gt;, strrpos(&lt;span style=&#34;color:#369&#34;&gt;$src&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;/wp-content&amp;#39;&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#369&#34;&gt;$post_ID&lt;/span&gt; = attachment_url_to_postid(&lt;span style=&#34;color:#369&#34;&gt;$url&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;list&lt;/span&gt;(&lt;span style=&#34;color:#369&#34;&gt;$url&lt;/span&gt;, &lt;span style=&#34;color:#369&#34;&gt;$width&lt;/span&gt;, &lt;span style=&#34;color:#369&#34;&gt;$height&lt;/span&gt;) = wp_get_attachment_image_src(&lt;span style=&#34;color:#369&#34;&gt;$post_ID&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;large&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$url&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#06b;font-weight:bold&#34;&gt;blurred&lt;/span&gt;(&lt;span style=&#34;color:#369&#34;&gt;$src&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#369&#34;&gt;$url&lt;/span&gt; = get_site_url() . substr(&lt;span style=&#34;color:#369&#34;&gt;$src&lt;/span&gt;, strrpos(&lt;span style=&#34;color:#369&#34;&gt;$src&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;/wp-content&amp;#39;&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#369&#34;&gt;$post_ID&lt;/span&gt; = attachment_url_to_postid(&lt;span style=&#34;color:#369&#34;&gt;$src&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;list&lt;/span&gt;(&lt;span style=&#34;color:#369&#34;&gt;$url&lt;/span&gt;, &lt;span style=&#34;color:#369&#34;&gt;$width&lt;/span&gt;, &lt;span style=&#34;color:#369&#34;&gt;$height&lt;/span&gt;) = wp_get_attachment_image_src(&lt;span style=&#34;color:#369&#34;&gt;$post_ID&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;blurred&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$url&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And now use it in the template like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;div&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;class&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;blurImg&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;div&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;background-image: url(&amp;#39;&amp;lt;?php echo MyTheme::non_blurred(get_theme_mod( &amp;#39;header&amp;#39; )); ?&amp;gt;&amp;#39;)&amp;#34;&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;div&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;background-image: url(&amp;#39;&amp;lt;?php echo MyTheme::blurred(get_theme_mod(&amp;#39;header&amp;#39;)); ?&amp;gt;&amp;#39;); opacity: 0;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;class&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;blur&amp;#34;&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;header&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;header&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Add CSS:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-css&#34; data-lang=&#34;css&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;.&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;blurImg&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;height&lt;/span&gt;: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;440&lt;/span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;px&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;left&lt;/span&gt;: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;position&lt;/span&gt;: &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;relative&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;top&lt;/span&gt;: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;width&lt;/span&gt;: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;100&lt;/span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;%&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;z-index&lt;/span&gt;: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;-1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;.&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;blurImg&lt;/span&gt; &amp;gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;div&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;background-position&lt;/span&gt;: &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;center&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;center&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;background-repeat&lt;/span&gt;: &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;no-repeat&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;background-size&lt;/span&gt;: &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;cover&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;height&lt;/span&gt;: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;440&lt;/span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;px&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;position&lt;/span&gt;: &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;fixed&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;width&lt;/span&gt;: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;100&lt;/span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;%&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;header&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;padding&lt;/span&gt;: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;20&lt;/span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;px&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;position&lt;/span&gt;: &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;absolute&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;top&lt;/span&gt;: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;width&lt;/span&gt;: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;100&lt;/span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;%&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;z-index&lt;/span&gt;: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Add JavaScript magic sauce to gradually replace the non-blurred image with the blurred version as the user scrolls:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    jQuery(&lt;span style=&#34;color:#038&#34;&gt;window&lt;/span&gt;).scroll(&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; H = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;240&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; oVal = jQuery(&lt;span style=&#34;color:#038&#34;&gt;window&lt;/span&gt;).scrollTop() / H;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      jQuery(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;.blurImg .blur&amp;#34;&lt;/span&gt;).css(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;opacity&amp;#34;&lt;/span&gt;, oVal);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }).call(&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;this&lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Generating the blurred version can be very strenuous on the server. I would oftentimes receive the error PHP Fatal error:  Maximum execution time of 30 seconds exceeded. There are ways to work around that. One way is to use a quicker method of blurring the image by shrinking it, blurring, and resizing it back. Another way is to use a background job, something like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;add_action( &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;add_attachment&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;array&lt;/span&gt;(&lt;span style=&#34;color:#369&#34;&gt;$this&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;wp_blur_attachment_filter&amp;#39;&lt;/span&gt;) );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;add_action( &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;wp_blur_attachment_filter_hook&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;wp_blur_attachment_filter_callback&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#06b;font-weight:bold&#34;&gt;wp_blur_attachment_filter_callback&lt;/span&gt;(&lt;span style=&#34;color:#369&#34;&gt;$path&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#369&#34;&gt;$path_parts&lt;/span&gt; = pathinfo(&lt;span style=&#34;color:#369&#34;&gt;$path&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#369&#34;&gt;$imagick&lt;/span&gt; = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; \Imagick(&lt;span style=&#34;color:#369&#34;&gt;$path&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#369&#34;&gt;$imagick&lt;/span&gt;-&amp;gt;&lt;span style=&#34;color:#369&#34;&gt;blurImage&lt;/span&gt;(&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;20&lt;/span&gt;, Imagick::&lt;span style=&#34;color:#369&#34;&gt;CHANNEL_ALL&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#369&#34;&gt;$imagick&lt;/span&gt;-&amp;gt;&lt;span style=&#34;color:#369&#34;&gt;modulateImage&lt;/span&gt;(&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;75&lt;/span&gt;, &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;105&lt;/span&gt;, &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;100&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#369&#34;&gt;$destination&lt;/span&gt; = dirname(&lt;span style=&#34;color:#369&#34;&gt;$path&lt;/span&gt;) . &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;/&amp;#34;&lt;/span&gt; . &lt;span style=&#34;color:#369&#34;&gt;$path_parts&lt;/span&gt;[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;filename&amp;#39;&lt;/span&gt;] . &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;_darken_blur.&amp;#34;&lt;/span&gt; . &lt;span style=&#34;color:#369&#34;&gt;$path_parts&lt;/span&gt;[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;extension&amp;#39;&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#369&#34;&gt;$imagick&lt;/span&gt;-&amp;gt;&lt;span style=&#34;color:#369&#34;&gt;writeImage&lt;/span&gt;(&lt;span style=&#34;color:#369&#34;&gt;$destination&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#06b;font-weight:bold&#34;&gt;wp_blur_attachment_filter&lt;/span&gt;(&lt;span style=&#34;color:#369&#34;&gt;$post_ID&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#369&#34;&gt;$path&lt;/span&gt; = get_attached_file( &lt;span style=&#34;color:#369&#34;&gt;$post_ID&lt;/span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  wp_schedule_single_event(time(), &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;wp_blur_attachment_filter_hook&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;array&lt;/span&gt;(&lt;span style=&#34;color:#369&#34;&gt;$path&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Or better yet, use cloud image processing—​I wrote about that &lt;a href=&#34;/blog/2015/12/image-processing-in-cloud-with-blitline/&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I hope you found this writeup useful!&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Pagination days are over? Infinite scrolling technique</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2013/11/pagination-days-are-over-infinite/"/>
      <id>https://www.endpointdev.com/blog/2013/11/pagination-days-are-over-infinite/</id>
      <published>2013-11-15T00:00:00+00:00</published>
      <author>
        <name>Marina Lohova</name>
      </author>
      <content type="html">
        &lt;p&gt;Love it or hate it, but the infinite scrolling technique became a big part of UX. Google Images use it, Flickr uses it, Beyonce’s official website uses it. Twitter, Tumblr and the Facebook feed have it as well. The technique allows users to seamlessly scroll through content. When the user reaches the end of the page new content will automatically load at the bottom.&lt;/p&gt;
&lt;p&gt;In my opinion, it allows for a much more natural and immersive experience while viewing images or articles, much better than pagination or once popular image slideshow galleries. In the real life you don’t click on pagination links to get through your day, right?&lt;/p&gt;
&lt;p&gt;To create the infinite scrolling page with images we will use the &lt;a href=&#34;https://github.com/paulirish/infinite-scroll&#34;&gt;jQuery Infinite Scroll&lt;/a&gt; plugin and &lt;a href=&#34;https://masonry.desandro.com/&#34;&gt;Masonry&lt;/a&gt; to lay out the images. Here is the &lt;a href=&#34;https://infinite-scroll.com/demo/masonry/&#34;&gt;demo&lt;/a&gt; of what we are going to accomplish. The code is below.&lt;/p&gt;
&lt;p&gt;First step is to include the necessary scripts:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;script src=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;/javascripts/jquery.min.js&amp;#34;&lt;/span&gt; type=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;text/javascript&amp;#34;&lt;/span&gt;&amp;gt;&amp;lt;&lt;span style=&#34;color:#080;background-color:#fff0ff&#34;&gt;/script&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;background-color:#fff0ff&#34;&gt;&amp;lt;script src=&amp;#34;/&lt;/span&gt;javascripts/jquery.masonry.min.js&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34; type=&amp;#34;&lt;/span&gt;text/javascript&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;lt;script src=&amp;#34;&lt;/span&gt;/javascripts/jquery.infinitescroll.min.js&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34; type=&amp;#34;&lt;/span&gt;text/javascript&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Add the container element. The navigation element is a very important part that triggers the loading of the subsequent page. After the second page the page number will automatically increment to fetch the subsequent pages:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;div&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;id&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;container&amp;#34;&lt;/span&gt;&amp;gt;&lt;span style=&#34;color:#a61717;background-color:#e3d2d2&#34;&gt;&amp;lt;&lt;/span&gt;% @images.each do |i| %&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;div&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;class&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;item&amp;#34;&lt;/span&gt;&amp;gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;img&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;src&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;...&amp;#34;&lt;/span&gt;/&amp;gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;div&lt;/span&gt;&amp;gt;&lt;span style=&#34;color:#a61717;background-color:#e3d2d2&#34;&gt;&amp;lt;&lt;/span&gt;% end %&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;div&lt;/span&gt;&amp;gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;nav&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;id&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;page-nav&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;display: none;&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;href&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;/?p=2&amp;#34;&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;a&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;nav&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ready to init the infinite scrolling script:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$(&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt;(){
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; container = $(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;#container&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    container.masonry({
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       itemSelector: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;.item&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    container.infinitescroll({
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      navSelector  : &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;#page-nav&amp;#39;&lt;/span&gt;, 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      nextSelector : &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;#page-nav a&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      itemSelector : &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;.item&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      loading: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          finishedMsg: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;No more pages to load.&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          img: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;http://i.imgur.com/6RMhx.gif&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt;( newElements ) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; newElems = $( newElements ).css({ opacity: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          newElems.animate({ opacity: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          container.masonry( &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;appended&amp;#39;&lt;/span&gt;, $newElems, &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  });&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once the user scrolls down to the bottom of the first page, the script will fetch the second page and filter out the new elements by the item selector. The controller action will look like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#06b;font-weight:bold&#34;&gt;list&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#33b&#34;&gt;@images&lt;/span&gt; = &lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Image&lt;/span&gt;.paginate(&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:page&lt;/span&gt; =&amp;gt; params[&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:p&lt;/span&gt;] || &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:per_page&lt;/span&gt; =&amp;gt; &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;25&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Finally, we will add the styles to create a three-column layout and some nice animations to render the newly loaded items:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-css&#34; data-lang=&#34;css&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;#&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;container&lt;/span&gt; .&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;item&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;width&lt;/span&gt;: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;33&lt;/span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;%&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;.&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;transitions-enabled&lt;/span&gt;.&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;masonry&lt;/span&gt; .&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;masonry-brick&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;transition-property&lt;/span&gt;: &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;left&lt;/span&gt;, &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;right&lt;/span&gt;, &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;top&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;.&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;transitions-enabled&lt;/span&gt;.&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;masonry&lt;/span&gt;, .&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;transitions-enabled&lt;/span&gt;.&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;masonry&lt;/span&gt; .&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;masonry-brick&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;transition-duration&lt;/span&gt;: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0.7&lt;/span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;s&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


      </content>
    </entry>
  
    <entry>
      <title>WAVE: Evaluating Web Accessibility</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2013/07/wave-evaluating-web-accessibility/"/>
      <id>https://www.endpointdev.com/blog/2013/07/wave-evaluating-web-accessibility/</id>
      <published>2013-07-01T00:00:00+00:00</published>
      <author>
        <name>Steph Skardal</name>
      </author>
      <content type="html">
        &lt;p&gt;It’s been far too long since I wrote a blog article! I’ve been a bit preoccupied:&lt;/p&gt;
&lt;img border=&#34;0&#34; src=&#34;/blog/2013/07/wave-evaluating-web-accessibility/image-0.jpeg&#34; style=&#34;width:700px;&#34;/&gt;
&lt;p&gt;My [cute] preoccupation.&lt;/p&gt;
&lt;p&gt;I thought I’d start with a short blog article to get back into it. Recently, the &lt;a href=&#34;https://cyber.law.harvard.edu/research/h2o&#34;&gt;H2O&lt;/a&gt; project has been keeping me busy. One interesting resource I’ve been using for the project is &lt;a href=&#34;http://wave.webaim.org/&#34;&gt;WAVE&lt;/a&gt;. WAVE is a free web accessibility evaluation tool, which in our case helps us evaluate the H2O web application for it’s use in an academic setting for those users with disabilities. The tool helps to evaluate accessibility against &lt;a href=&#34;https://en.wikipedia.org/wiki/Section_508_Amendment_to_the_Rehabilitation_Act_of_1973&#34;&gt;Section 508&lt;/a&gt;, &lt;a href=&#34;https://www.w3.org/WAI/intro/wcag.php&#34;&gt;WCAG&lt;/a&gt;, and more.&lt;/p&gt;
&lt;p&gt;To use the tool, you simply visit &lt;a href=&#34;https://wave.webaim.org/&#34;&gt;https://wave.webaim.org/&lt;/a&gt; and type in the URL that you want evaluated:&lt;/p&gt;
&lt;img border=&#34;0&#34; src=&#34;/blog/2013/07/wave-evaluating-web-accessibility/image-1.png&#34;/&gt;
&lt;p&gt;WAVE homepage&lt;/p&gt;
&lt;p&gt;The results show warnings and alerts, as well as feedback regarding other structural markup overlayed on the page. The results are meant to serve as a resource to make decisions regarding accessibility. Some web application developers may be more inclined to follow the guidelines than others.&lt;/p&gt;
&lt;img border=&#34;0&#34; src=&#34;/blog/2013/07/wave-evaluating-web-accessibility/image-2.png&#34; style=&#34;width:730px;&#34;/&gt;
&lt;p&gt;Example WAVE output, analysis of End Point’s home page.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Honor your elders (and others)</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2013/05/honor-your-elders-and-others/"/>
      <id>https://www.endpointdev.com/blog/2013/05/honor-your-elders-and-others/</id>
      <published>2013-05-24T00:00:00+00:00</published>
      <author>
        <name>Jeff Boes</name>
      </author>
      <content type="html">
        &lt;p&gt;What an odd topic, you might be thinking. &lt;a href=&#34;/blog/2011/06/seniornet/&#34;&gt;Some time ago,&lt;/a&gt; I began teaching my elders (who are rapidly becoming my contemporaries, curse you, Father Time) how to navigate the Internet. After almost two years of this, I’ve become more sensitized to seeing the ’Net from their POV.&lt;/p&gt;
&lt;p&gt;Here’s a small fragment of a page I saw today (actual size):&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;/blog/2013/05/honor-your-elders-and-others/image-0-big.png&#34; imageanchor=&#34;1&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2013/05/honor-your-elders-and-others/image-0.png&#34;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The page numbers and arrowheads are the only navigation elements on this &lt;strong&gt;huge, complex&lt;/strong&gt; page that allow you to move around the list of products. The numbers are an 11px font, and the arrowheads are 6px by 8px. For a senior citizen, or indeed anyone with less than perfect vision and eye-hand coordination, this is pretty challenging to find on the page, let alone use.&lt;/p&gt;
&lt;p&gt;Here’s a quick challenge for you as web designer: try dimming your screen down a bit, to the point where it’s noticeably uncomfortable to use. Does your page still seem easy to navigate? Now try switching from your dominant hand to your other hand, and try clicking your page navigation elements (not just buttons, but drop-downs, menus, etc.). Still usable? If not, you may be designing your page to keep away some users. Just a thought.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Data binding in web applications</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2013/03/data-binding-in-web-applications/"/>
      <id>https://www.endpointdev.com/blog/2013/03/data-binding-in-web-applications/</id>
      <published>2013-03-06T00:00:00+00:00</published>
      <author>
        <name>Kamil Ciemniewski</name>
      </author>
      <content type="html">
        &lt;p&gt;Ever since JavaScript was introduced the world of web programming has constantly been visited by creative minds, ready to solve burning problems web developers were experiencing.&lt;/p&gt;
&lt;p&gt;Navigating through some of the oldest web-dev articles, we still can feel the pain of manipulating the DOM, the-vanilla-way and creating ever so clever hacks to make the code work on all of the web browsers.&lt;/p&gt;
&lt;p&gt;Then, the JS-frameworks epidemy started to disseminate. As web developers—​we started to have some powerful tools. The productivity of average nerdy Joe raised immediately as he started using Prototype.js, Mootools or jQuery.&lt;/p&gt;
&lt;h3 id=&#34;evolution-of-ui-programming-for-browsers&#34;&gt;Evolution of UI programming for browsers&lt;/h3&gt;
&lt;p&gt;And so was the start of our global evolution, towards making the Web—​our main means of interacting with &lt;em&gt;users&lt;/em&gt;. Few with big enough imagination were already seeing HTML, CSS &amp;amp; JS as the future standard of creating user interfaces for data-rich applications. Of course, there were toolkits like ExtJS which aimed at nothing but this—​but who would have known back then, that we will be able to build apps for &lt;strong&gt;smartphones&lt;/strong&gt; the way we are building ones for the web?&lt;/p&gt;
&lt;p&gt;I have my little observation in life — all &lt;em&gt;hot stuff&lt;/em&gt; in the world of technology, has their &lt;em&gt;phases&lt;/em&gt;. That is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;they don’t exist, but the problems they solve do and they are &lt;strong&gt;painful&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;they show up, and there is massive excitement&lt;/li&gt;
&lt;li&gt;people discover new pains brought on by these solutions&lt;/li&gt;
&lt;li&gt;next pain and the beginning of the “next big thing”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At the beginning it was painful to write all the &lt;em&gt;vanilla&lt;/em&gt; JS code without shooting yourself in your own foot (well let’s just say it—​JS is Pandora’s Box of pain). Then you realize you’ve got your sweet &amp;amp; cool jQuery with which you can rapidly impress your boss/clients/friends.&lt;/p&gt;
&lt;p&gt;Your next surprise is that jQuery isn’t enough—​ever wondered why frameworks like Backbone.js were created?&lt;/p&gt;
&lt;h3 id=&#34;data-binding&#34;&gt;Data binding&lt;/h3&gt;
&lt;p&gt;Here is the challenge for you: write GMail web interface clone using &lt;strong&gt;just&lt;/strong&gt; jQuery. Here is the spoiler: you’d get lost in managing your app’s &lt;em&gt;state&lt;/em&gt;—​and syncing UI to reflect it. In fact—​even small &lt;em&gt;todo&lt;/em&gt; app in vanilla jQuery could end up being quite complicated. That’s just not the way other folks are dealing with UI programming. A long time ago they were—​but they got smarter. We can learn from their mistakes.&lt;/p&gt;
&lt;p&gt;When I was beginning my journey into the world of professional programming, I was given a job of creating small business app with C# and Windows Forms. Despite the fact that in the world of desktop apps you have to be mindful of much more things than in the web/browsers world (like updating your UI only on the main thread), the whole experience was pretty much the same as with vanilla jQuery:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;create inputs&lt;/li&gt;
&lt;li&gt;subscribe to its events&lt;/li&gt;
&lt;li&gt;write the code that updates inner state based on the state of those inputs/controls&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This quickly gets very cumbersome as the needs grows. You could have a requirement that one set of inputs is dependent on another, which results in hours of coding and more hours in debugging.&lt;/p&gt;
&lt;p&gt;The way &lt;em&gt;smart devs&lt;/em&gt; are tackling this issue is called &lt;em&gt;data binding&lt;/em&gt;. This simply means that you &lt;em&gt;tell&lt;/em&gt; specific portion of UI to be &lt;em&gt;bound&lt;/em&gt; to some value from your business layer.&lt;/p&gt;
&lt;p&gt;One nice example of the framework that employs data binding is Apple’s Cocoa. The XCode even has nice visual tools to bind controls values with the data model. Another example of the framework that uses data binding extensively is WPF (Windows Presentation Foundation). The concept widely used there is the MVVM pattern (Model-View-ViewModel). Which basically says that you have your &lt;em&gt;view model&lt;/em&gt; object and UI which is bound to the view model and all you have to do is to manage the view model.&lt;/p&gt;
&lt;p&gt;That’s the core concept here: &lt;strong&gt;managing objects is much easier than managing UI&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;data-binding-on-web&#34;&gt;Data binding on web&lt;/h3&gt;
&lt;p&gt;As the need came with more and more amazing web apps—​we now have quite a nice choice when it comes to employing data binding:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://angularjs.org/&#34;&gt;Angular.js&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Angular.js is one of the most impressive front-end JS frameworks there are. It has view templates mechanism allowing you to specify your binding statements directly there.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://emberjs.com/&#34;&gt;Ember.js&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Ember.js gains popularity every day. Just like Angular.js, it has its own nice templating language (handlebars) which allows you to have your binding statements in the markup too.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://knockoutjs.com/&#34;&gt;Knockout.js&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you don’t want to use any of those frameworks, that’s a great library that implements &lt;strong&gt;just&lt;/strong&gt; the data binding part.&lt;/p&gt;
&lt;p&gt;It uses the concept I described before: the MVVM pattern. You are simply defining your view model and telling your UI where it has to get values from and that’s it.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://backbonejs.org/&#34;&gt;Backbone.js&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;But what if you’d like to use Backbone.js? It’s true that it’s amazing and is one neat piece of technology you can use to build your next ‘cool app’. There is one caveat though: you have to set up all the niceties that comes along with Angular.js or Ember.js yourself, including all the sweetness of data binding.&lt;/p&gt;
&lt;h3 id=&#34;backbonejs--knockoutjs&#34;&gt;Backbone.js + Knockout.js&lt;/h3&gt;
&lt;p&gt;Here is a simple way of using Knockout.js with Backbone.js:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;for every Backbone.js view, define complementary view model&lt;/li&gt;
&lt;li&gt;put ‘ko.applyBindings’ at the end of your ‘render’ method&lt;/li&gt;
&lt;li&gt;do not use any logic in your view templates—​rely solely on data binding&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So for example:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;# app/views/photos.coffee&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;module.exports = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;PhotosView&lt;/span&gt; extends Backbone.View
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   template: require &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;./templates/photos/index&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   initialize: () -&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#555&#34;&gt;@view&lt;/span&gt; = new PhotosViewModel()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#555&#34;&gt;@render&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   render: () -&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#a61717;background-color:#e3d2d2&#34;&gt;$&lt;/span&gt;(&lt;span style=&#34;color:#555&#34;&gt;@el&lt;/span&gt;).html(&lt;span style=&#34;color:#555&#34;&gt;@template&lt;/span&gt;())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       ko.applyBindings(&lt;span style=&#34;color:#555&#34;&gt;@view&lt;/span&gt;, &lt;span style=&#34;color:#555&#34;&gt;@el&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   setData: (photos) =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#555&#34;&gt;@view.photos&lt;/span&gt;(photos)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;PhotosViewModel&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   constructor: -&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#555&#34;&gt;@photos&lt;/span&gt; = ko.observableArray []&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;# app/views/templates/photos/index.eco&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;ul&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;data-bind&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;foreach: photos&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;li&lt;/span&gt;&amp;gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;img&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;data-bind&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;attr: {src: $data.get(&amp;#39;url&amp;#39;)}&amp;#34;&lt;/span&gt;/&amp;gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;li&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;ul&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now—​with this, you can manipulate the photos array however you like, and the UI will get auto-updated on its own. But this idea shines the most when it comes to implementing forms with it. Take a look at the following example:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;# app/views/company.coffee&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;module.exports = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;EditCompanyView&lt;/span&gt; extends Backbone.View
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   template: require &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;./templates/companies/edit&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   initialize: () -&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#555&#34;&gt;@view&lt;/span&gt; = new EditCompanyViewModel()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#555&#34;&gt;@render&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   render: () -&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#a61717;background-color:#e3d2d2&#34;&gt;$&lt;/span&gt;(&lt;span style=&#34;color:#555&#34;&gt;@el&lt;/span&gt;).html(&lt;span style=&#34;color:#555&#34;&gt;@template&lt;/span&gt;())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       ko.applyBindings(&lt;span style=&#34;color:#555&#34;&gt;@view&lt;/span&gt;, &lt;span style=&#34;color:#555&#34;&gt;@el&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   setData: (company) =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#555&#34;&gt;@view.company&lt;/span&gt;(company)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;EditCompanyViewModel&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   constructor: -&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#888&#34;&gt;# let’s instantiate it with default company for bindings to work&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#555&#34;&gt;@company&lt;/span&gt; = ko.observable(new Company())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#555&#34;&gt;@companyName&lt;/span&gt; = ko.computed
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;           read: =&amp;gt; &lt;span style=&#34;color:#555&#34;&gt;@company.get&lt;/span&gt;(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;name&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;           write: (value) =&amp;gt; &lt;span style=&#34;color:#555&#34;&gt;@company.set&lt;/span&gt;(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;name&amp;#39;&lt;/span&gt;: value)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#555&#34;&gt;@companyState&lt;/span&gt; = ko.computed
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;           read: =&amp;gt; &lt;span style=&#34;color:#555&#34;&gt;@company.get&lt;/span&gt;(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;state&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;           write: (value) =&amp;gt; &lt;span style=&#34;color:#555&#34;&gt;@company.set&lt;/span&gt;(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;state&amp;#39;&lt;/span&gt;: value)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#555&#34;&gt;@companyWebsite&lt;/span&gt; = ko.computed
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;           read: =&amp;gt; &lt;span style=&#34;color:#555&#34;&gt;@company.get&lt;/span&gt;(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;url&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;           write: (value) =&amp;gt; &lt;span style=&#34;color:#555&#34;&gt;@company.set&lt;/span&gt;(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;url&amp;#39;&lt;/span&gt;: value)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#555&#34;&gt;@saveCompany&lt;/span&gt; = () =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;           &lt;span style=&#34;color:#555&#34;&gt;@company.save&lt;/span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;# app/views/templates/companies/edit.eco&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;form&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;data-bind&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;submit: saveCompany&amp;#34;&lt;/span&gt;&amp;gt;Name:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;input&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;data-bind&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;value: companyName&amp;#34;&lt;/span&gt;/&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   State:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;input&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;data-bind&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;value: companyState&amp;#34;&lt;/span&gt;/&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   Website:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;input&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;data-bind&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;value: companyWebsite&amp;#34;&lt;/span&gt;/&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;button&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;type&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;submit&amp;#34;&lt;/span&gt;&amp;gt;Submit&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;button&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;form&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;other-solutions&#34;&gt;Other solutions&lt;/h3&gt;
&lt;p&gt;These are quite contrived examples to give you an idea of how it can be done. The beauty of Backbone is that it allows you to structure your code however you’d like. And there are plenty of other solutions for this. Most notable:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://kmalakoff.github.com/knockback/&#34;&gt;http://kmalakoff.github.com/knockback/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://rivetsjs.com/&#34;&gt;http://rivetsjs.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/DreamTheater/Backbone.DataBinding&#34;&gt;https://github.com/DreamTheater/Backbone.DataBinding&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

      </content>
    </entry>
  
</feed>
