Encounter
One day in 2020, I was searching for and browsing among a list of graduate programs. I stumbled across the official website of Koninklijke Academie van Beeldende Kunsten (Royal Academy of Art), Den Haag, where the logo on the webpage captured me. The institution’s logo is a minimalistic crown figure connected by seven discrete points. It is unique: every time I refresh the page, the connection pattern between the dots randomly alters.
Since then, the conception of variable logos came into my sight. Some people call them dynamic logos. Probably there isn’t a unified term, but they do seem increasingly prevalent in the new design trends.
Later in September 2020, I was invited to the NkHistory Project as the lead designer and web dev engineer. The project itself is initiated by a group of Nankai alumni and was relatively small in scale, but I was granted the maximum creative freedom. A perfect chance to try something different! I joined the team and started to plan on a branding identity with an experimental variable logo present.
Sketch
NkHistory is designed as a platform for Nankai alumni to share their campus memories. All memories submitted will have corresponding time and location information. As we collected a sufficient amount of memories, users will be able to browse them in a timeline view or a spatial view. The essential idea is kind of like i remember, but different.
With that concept, inspirations of the logo came quite naturally. We brainstormed several linkable visual elements and settled on an ✳ shaped iteration. This logo put together two things: The Nankai Octogram, which is the official logo of the Nankai University and Nankai High School; as well as the asterisk symbol, which represents “any”.
That being said, a strictly upright and regular eight-spoked asterisk is inevitably dull. Here, What we are going to do is to randomly disorganize the strokes in it, endowing it with a certain level of casualness and a nonchalant attitude.
Implement
To draw logos like this on the webpage, there are several routes to choose from. In the following section, we’ll discuss three of them.
Div + CSS: Simple & Brutal
Considering the logo essentially consists of a group of rectangles, one option is to use a lot of div
s and transform
them using CSS. In fact, that’s the way we chose when experimenting with our initial demo posters:
In this poster, randomly generated logos are arranged in 17 rows and 13 columns. The first logo is a strictly standard eight-spoked asterisk, and as the index increments, the randomness increases along. To implement this, we could simply define a Vue Component box
, specifying a prop randomi
as a quantified “randomness factor”:
1 | Vue.component('box', { |
In each black box
, there are four bar
s, all of which are made up of four rectangles with two layers of transformation.
- The first layer of transformation is solely rotation, with the
i
-th bar rotated to a degree of45i
, so the bars could be at 0, 45, 90, and 135 degrees, respectively, to constitute an asterisk; - The second layer is all about randomization, including randomized translation and further rotation.
It is known that the current CSS standard lacks support for multilayered transformations applied on one single element. We could manually calculate the transformation matrices, but that would unnecessarily complicate our codebase. We decided to use wrappers.
The following code only suggests the second layer transformation because the first layer is “static” and could be defined in the stylesheets.
1 | // Generate r by randomness factor |
After setting the container element, we then iterate this process 221 times. Each iteration would push a box with an incremented randomness factor. Note that overflow: visible
is required to achieve the effect of boxes travelling out of their bounds.
1 | let boxes = [] |
SVG: Better Performance
Let alone the demo code above, recall that these variable logos need to be present in large numbers on an actual webpage. The first thing is to determine a more reliable approach for a production environment.
Experience has shown that when a large quantity of simple geometric figures recur, compared to using <div>
, SVG is obviously a more adaptable and performant solution. Secondly, considering all possible use cases for this logo, we need it better designed and encapsulated. E.g. configurable parameters such as size
, colors
and so on, should be exposed.
Yep - again, we’ve come to the transition zone between research and engineering. Time for a change.
This time, we use Vue’s Single File Components. Since the logo is apparently important and reused all across the project, we need to specifically give it a name. How about “Wanderer”?
When designing the inner layout structure of the Wanderer Component, we encountered a problem. Unlike div
, which could be defined an overflow: visible
CSS property, elements inside SVG cannot be drawn outside the box, at least in some browsers. We could use another pattern to circumvent this: wrapping the SVG in a smaller div
centrally. This way, we could allow the elements inside those SVG to wander out of the outer div
, but still, on the SVG render area.
In the design draft, a large portion of use cases for this logo involve a background box accompanied (See Figure 4). Hence we wish the box could be a configurable option for this component. Meanwhile, the box is not necessarily a regular square; it could be randomly distorted as well.
The main render process is written as
1 | // Code inside mounted(): |
where randomizeArray
is a function placed in the methods()
block. We use it to randomize every value inside an array.
1 | randomizeArray: function (arr) { |
After taking care of the outside trapezoid, the inner bars (rectangles) could be implemented as follows:
1 | let groups = new Array(4).fill(draw.group()); |
During the render process, the values in Figure 5 are used to calculate the correct element sizes and offset values. We also utilized SVG.js to manipulate SVGs inside the element more concisely and clearly.
Just for fun, we could add an animation option to let it swag:
1 | if (animate) { |
Canvas: Lack of Interactivity
When doing graphics on the webpage, SVG is always brought up along with HTML5 Canvas. From a pure performance first perspective, Canvas provides more low-level APIs together with pixel-wide graphic controls and should be more performant. The primary concern here is that Canvas cannot provide straightforward and easy-to-use interactivity functionalities as SVG, nor could it adapt stylesheets and embed in HTML docs seamlessly. At this point, therefore, we do not consider using Canvas.
The phase II of NkHistory might introduce recording memories by precise spatial location, allowing users to specify where their memories occurred on a 3D model of campus buildings. That’s probably when Canvas would come in handy.
Run
Now the components are all set, to quickly wrap up all the exposed props:
size
- Semantical size of the Wanderer component.randomness
- Irregularity of Wanderers’ shape.barThicknessCoefficient
- Default to 0.125, which means the length of the bars will be 8 times their width.bleedCoefficient
- Default to 0.5, which means the length of the SVG area (inner container) will be 1.5 times the actual component size (outer container) exposed to the parent. Note that for Wanderers with higher randomness, you probably would want to set a higher bleed coefficient as well to prevent drawing out of SVG bound.palette
- An array with two color strings, acting as the foreground and the background colors, respectively.polygonScale
- Size of the background polygon.polygonRandomness
- Irregularity of the background polygon.animate
- The animation playback cycle. Setting this to 0 disables the animation.
Let’s experiment with different combinations of props, see what possibilities we can expect.
First, we tried using v-for
to render Wanderer 50 times with incremental barThicknessCoefficient
s:
1 | <Wanderer |
Hmm. Looks like it does the job. Next, we’ll try to reimplement the poster given in Figure 4. To do this, we need to specify the correct colors, utilize the background polygon, and then use a sequence of increasing randomness
. This time, however, Math.pow()
is used instead of an arithmetic sequence. I can already feel how scary exponential growth is.
1 | <Wanderer |
I’m quite curious about randomizing as many configurable metrics as we could. This is what we’d get if we insert Math.random()
in the colors, size, thickness coefficient, and even in the randomness prop itself.
It’s a pity that NkHistory is only a small project. If it’s a Silicon Valley tech giant, this is an excellent chance to embed its multicultural inclusion & diversity values.
1 | <Wanderer |
If you’re sufficiently imaginative, you could even tweak the Wanderer into something with an unique artistic flavour that you can’t recognize:
1 | <Wanderer |
Above are static images only. To see it animated, just pass a number to animate
. With a few lines of CSS, you could apply some interactivity to it as well:
Random Thoughts
Looks like we’ve gone pretty far, but in fact, merely a total of 200 lines of code were written. No complex logic or design patterns were involved. It’s just that experimenting with interactive graphics is fun. That’s why I’m writing this article.
I would like to end it by pointing out that this is not quite a helpful technical blog post. Usually, we are not encouraged to consume so much time and energy in these perceptual, “uncertain” aspects in a typical project. For most software engineers working for a large company, the plan for each project is rather fixed, and the works of people from different backgrounds are highly decoupled. It’s already rare for a software engineer and a UI/UX designer to work closely and carry out in-depth communications, let alone the less relevant branding division.
Having said that, participating in small projects like this with a small group of people - or only myself - is genuinely an enjoyable experience. Not having a fixed plan makes it possible for new ideas to constantly come out even in the development process. For example, after discovering that randomizing all possible props outputs so many distinct and characteristic graphics, we started to consider assigning a unique random Wanderer to each memory. This way, users will identify it more easily when later browsing among all memories through either the timeline or the spatial view. They may also specify the Wanderer’s appearance associated with their memories. Of course, this might mean that we need to destructure and isolate the randomness
prop, injecting new props such as the exact positioning data of the bars.
Soon I’ll be back as a software engineer, again - working on boring business logic on a daily basis. Nevertheless, I’ll be actively looking for the next escape.