The idea of making a weather card component by myself has actually been there for a long time, but it is still very difficult to do (layout, data source, weather display, adaptability), and it was eventually abandoned. Recently, the topic of the school club interview is to make a weather card, which happens to be an opportunity for me to take advantage of the National Day holiday to study it carefully. So today's article came into being.
~ (In fact, I basically solved the problem before the National Day holiday) ~ The current version of DouWeather is hosted in a temporary repository (https://github.com/daidr/DouWeather-temp) because I did not consider the code structure, which makes it difficult to maintain in the later stage. You can watch this repository (https://github.com/daidr/DouWeather) to follow the progress. I will take some time to refactor the code and update it in this repository.
Ⅰ. Design Concept Phase#
1. Source of Inspiration#
I positioned DouWeather (referred to as DW) as a web widget, and based on this consideration, I referred to iOS system widgets, the new version of MIUI system widgets, HarmonyOS widgets, and Windows 11 widgets. I found that they all have one thing in common: flat design, rounded corners, the use of sans-serif fonts, simple element styles, and all of them use gradients in backgrounds or icons, making the widgets appear more dynamic. Among them, Windows 11 widgets added a light shadow, probably to make the widgets stand out from the acrylic background.
So, I followed suit and designed the sunny weather icon for DW, and used XD to design the first card style (the current medium card style of DW).
Note: The design of DouWeather is also inspired by the design styles of Google AdSense (MD2), Mew, and other websites.
Shrinking the medium-style card resulted in a square small-style widget.
When I had the idea of writing a weather card, I also made a sketch. At that time, I referred to the Material Design style. I happened to find it when I went home during the National Day holiday, and I don't know how long ago the initial draft of the weather card was... it's quite abstract...
2. Icon Design#
Many elements in DW are inspired by Xiaomi Weather, including the icons. I tried to keep the overall style of the icons simple, using large gradient backgrounds to highlight the weather characteristics. Before starting the development, I made three icons for sunny, cloudy, and partly cloudy, following the format of Xiaomi Weather, which would be convenient for future design and development. The format used is still SVG, which controls the overall size of the component and ensures fast loading.
During the development process, inspired by my roommate, I tried to add some animations to the weather icons, but some of them were too distracting, so I abandoned the idea.
3. Normal Style and Detail Style?#
Before development, I actually only planned to create two styles (small and medium). The main reason for creating the normal style was that during the development process, I found that when the medium style is placed above an element with a large width, it appears empty and not aesthetically pleasing. So, based on the medium style, I increased the width and added air quality, sun protection advice, and other data displays. As for the detail style, it was purely because I deeply loved the trend forecast in Xiaomi Weather and wanted to replicate it in DW.
Ⅱ. Development Phase#
1. How to Use Web Components Gracefully without Using Build Tools?#
Before writing the weather card, I had only used Web Components once, which was in the Genshin Impact player information query (https://ys.daidr.me). At that time, because there were many repetitive elements (character information), I wanted to try using this new thing to encapsulate them. The lesson I learned was: if you don't use build tools and want to develop gracefully, the template tag is essential, otherwise maintaining the code would be a nightmare.
2. How to Display Icons Gracefully?#
Weather icons will be extensively reused in the card (especially in the detail style). If there is no simple way to call the icons, it will be difficult to maintain. In addition, I only designed three icons during development, and I need to consider how to easily add, delete, and modify icons in the future, minimizing the coupling between icon retrieval and main code. In frontend development, there are generally several ways to import icons:
① Use @font-face to import icon font files#
This method is commonly used for large icon fonts, such as Font Awesome and Material Icons. The advantage is that it is intuitive to use and can directly modify the appearance of icons using font-size or color. Thanks to the browser's support for colr, colored icon fonts can also be used. However, the disadvantage is also obvious: it is difficult to maintain, especially when it comes to gradient fills. Currently, there is no font-making software that can elegantly accomplish this task. In addition, the hook logic of some custom fonts on mobile phones may cause the icons introduced by this method to fail.
However, in DW, some icons are indeed imported using this method, such as the wind direction icons. The icons are monochrome and the number is fixed and does not need to be modified frequently (8 directions), which is very suitable for this method.
② Use svg symbol#
This is also a commonly used way to import icons, and it has excellent compatibility. It is relatively easy to maintain and can support some animations. AI can directly export icons as symbol tags, and many build tools can also provide support for this, so there are basically no disadvantages.
However, the weather icons in DW did not use the above two methods. I encapsulated the weather icons using Web Components, which already has a similar effect to symbol, so using symbol would be redundant.
Calling the encapsulated weather icons is much more convenient. You can directly use <dw-icon type="sunny"></dw-icon>
to call a specific icon. Here is an example.
I plan to use gulp in the future, which can also provide convenience for development.
3. How to Implement Adaptivity?#
During the development of the weather component, I realized that restoring the design draft is actually the easiest part. I need to ensure that all elements in the card can be displayed in an orderly manner. I originally wanted to fix the width of each card style, which would ensure that the layout of the card is always perfect. However, this would greatly reduce the versatility of the weather card, and other users of DW would not modify their layout specifically for a widget. At the same time, a fixed width means that the experience of the weather card on mobile devices will be very poor. But how can I achieve adaptivity?
The most commonly used method of adaptivity is to write media queries, but I cannot use media queries because I cannot know where other developers will insert the card, how they will insert the card, and what state the parent element of the card is in. I cannot determine the current state of the weather card based solely on the screen size.
I also cannot hide or show certain elements based on the width of the card, because in future versions, DW will modularize the data display part, allowing other developers to customize which data to display. Modifying the displayed elements without authorization may cause other developers' configurations to not be displayed as expected.
Since we are talking about judging the size of the parent container, let's talk about the implementation method. Generally speaking, the method I often use is to embed an iframe in the parent container, and listen for changes in the size of the iframe to determine the size of the container. Perhaps in the future, I can also try CSS container queries, which can provide great convenience, but currently, this feature is still in the PR stage... Compatibility List.
At one point, I wanted to fix the width of the card. In fact, even after I wrote almost all the layout and logic of DW, I still didn't find a good solution.
What inspired me was Windows Explorer:
The main elements of the weather card are fixed on the left and do not move, and the data display on the right side shows a scroll bar based on the width of the card. The implementation is very simple because I used flex layout. I just need to wrap the original data display area with a container that has the style flex-grow: 1;
.
4. Data Display in Detail Style#
This part is also very complex because the task for the club interview mentioned chart display. At that time, I wanted to replicate the 15-day trend forecast of Xiaomi Weather to test the waters. If it succeeded, I could expand it to hourly forecasts and other chart displays in the future. The chart part is implemented using SVG, so that dark mode styling can be easily handled. SVG is used instead of canvas because I want to use the browser's native JavaScript to draw the chart. I only need to draw a line chart, and Chart.js is obviously too heavy and bloated for this task. The original design used a difficult way to insert the chart in the middle, so later I separated the morning data, the chart, and the evening data. Since the column width is consistent, there is no need to worry about misalignment.
Then it's time to draw the chart. First, calculate the X coordinates of the data points uniformly, and then determine the Y coordinates of each data point based on the temperature. The data points are represented by SVG circle elements, and the line segments are drawn using the path element.
The syntax logic of the d parameter of the path is actually similar to the logic of drawing on canvas. First, use the M (MoveTo) command to move the starting point to the position of the first point, and then use the L (LineTo) command to draw the remaining line segments.
4. Dark Mode#
By using the media query @media(prefers-color-scheme: dark)
, the card style in dark mode can be defined.
It is worth mentioning that I used CSS variables, which are now compatible with most browsers, greatly reducing duplicate code.
Sometimes, users may not want the media query to modify the card style on its own, so I provide the theme
attribute to control the card color. You can use theme="light"
or theme="dark"
to lock the card in light mode or dark mode. I thought of implementing this small feature entirely using CSS. I didn't use Web Components much before, and I thought I could easily handle it with host selectors. So I wrote the following CSS...
:host {
// Default style
}
:host[theme="dark"] {
// Dark mode style
}
However... it failed. After searching through MDN, I found this selector (). So the correct way to write it should be like this (so, which of the little friends who have looked through the Blink source code can tell me why it was designed like this...):
:host {
// Default style
}
:host([theme="dark"]) {
// Dark mode style
}
Ⅲ. Conclusion#
Writing DW has taught me a lot. I rarely did chart generation by myself before, often just using chartjs or echarts. I also have a more comprehensive understanding of Web Components, and I have also become familiar with the use of flex layout~. At least, I didn't waste my time watching Xiaomi Weather for two days. My classmates thought I was crazy when they saw me constantly checking Xiaomi Weather on my phone~.