Transcript
This transcript was autogenerated. To make changes, submit a PR.
You everyone uses apps on their
machine that somehow just feel off. Maybe the
mouse works slightly different, or menus look weird,
or maybe you can't even articulate it. Something's just
wrong. There are a million tiny things that can
make an app feel off, regardless of which framework it's built in.
But if you know what to look for, you can make your app feel great
on on all platforms, this talk is about Electron,
but I don't want to spend too much time on what Electron is, so here's
a quick run through Electron is a framework
to create cross platform apps using web technologies.
It came out of GitHub, where it was developed for their code editor,
Atom. Electron combines a recent version of Chromium
node and a set of operating system specific APIs.
In essence, they combine the power and freedom of developing for
the web with the right apps to interact with your operating system,
with the file system, applications, et cetera.
What electron does is not new per se. There have been many attempts
at this going back to Adobe Air in 2008. What I think makes
electron different, and why it has seen such widespread adoption,
is that Electron gets most of the things right.
Building and packaging apps is straightforward and even doable.
Cross platform and the chosen abstractions make it easy
to port over web applications. The skill set
you already have if you develop for the web or with node,
can be used to create electron apps.
Here's an example of the code you need to show the Conf 42
schedule as an app. Notice it's not that many lines
of code and it's all just regular javascript.
Basically, we start an app, then we create a browser
window for that app. And lastly, when all the windows
are closed again, we also quit the application.
Now back to this idea of cross platform applications.
We all know all these operating systems have pretty different
interfaces. The good and the bad news is
that the devil is in the details when it comes to building good uis
for all platforms. This is good news because
it means if you want to get it right, you don't need a giant
redesign of your app to make it conform 100% to
each platform. You don't need to reimplement everything
using native widgets, and you can keep your app's
unique style. We have web apps
and mobile devices to thank for that. Because of web apps
and mobile devices, users have grown more tolerant and welcoming
to different types of interfaces.
If something like Gmail would have launched as a Mac app,
it would have been disregarded in an instant. But superhuman,
launched 13 years later, comes just fine with a very
custom interface. So more custom app UI
are much more acceptable nowadays, but you do need to
pay attention to the details.
This is especially interesting for electron app apps because they
tend to have a much more custom UI than apps built in
other frameworks using native widgets. And strangely
enough, the fully custom UI that an app like Slack has
is actually a benefit. By creating a consistent interface
for slack across different platforms, it actually becomes
easier to use for your users. For each platform,
however, it will need to take over some of the customs that
that particular operating system uses, and the combination of
this custom UI that works well with what you expect from your desktop
is what makes or breaks an application.
In this talk, I'll walk you through eight design and implementation
details I think matter the most. Tell you how to think about
them and how to solve them through code and design first
off, a little about me my name is Kilian Valkov. For the
past 20 years, I've developed websites, web applications and desktop
software. I'm also part of the electron governance team.
For the past ten or so years, I've used a number of different technologies
to publish desktop applications. I've used QT
and I've also used GTK. But before
I moved to Electron, these were just theoretically cross platform.
There was nothing much in the code itself to prevent them from being run on
different platforms, but the process of building and packaging
was just too opaque for me. Then after discovering Electron,
suddenly it was easy for me to distribute apps on all platforms,
and I've created dozens of them, open source and for clients alike.
Electron made that possible for me, and it also helped me focus
on the real cross platform details.
So here's eight ways to make your electron app feel great.
On all three platforms,
we all know what it looks like to load a web page.
You stare at a white page for a while, then things start to pop in,
and after some time, everything's loaded.
We also know how loading an app works. You stare at the
icon bouncing in your dock for a while, and then the
app pops into view, fully formed.
Because electron essentially loads a web page, it'll try
to do the former. We want to do the letter,
and there's two things we need to do for that.
So if we go back to the quickstart example, what's happening
here is that it shows the window and then starts loading the page.
What we want to do is flip that around.
We want to wait, showing the app until the page we're
showing has fully loaded. Electron gives us a handy
event for this, called ready to show when we create a
new window using browser window. We initially hide
it with show colon false. Then we wait for
the ready to show event and at that point we show the window.
This guarantees your page has loaded before the windows is shown.
The other thing we want to do here is focus the window once it's
shown. This is what happens for native Windows two and
it lets users interact with it straight away.
The second thing we want to do here is set a background color on your
browser window instead of the default white for window backgrounds
while pages are loading. The background of your window now follows
the background of the rest of your application, making it feel much
more cohesive and less like a web page.
If you have an app that takes a while to load and want to make
it feel faster, you can also opt to show the page
with a custom background color before the ready to show event
so something is already visible on the screen and animating your UI
or show a skeleton screen as soon as possible. The time
to using your app might be the same or even slightly longer,
but because stuff is happening on the screen it'll feel better for
your users.
We're skipping all the way to the end now, but the way your app closes
is just as important as the way it opens. And it's here
where something slightly different happens on Windows versus Mac
conceptually, on Windows and Linux the window
is the application. On Mac, however, the window
is just an instance of the application. What this
means is that on Windows if you close the app window,
you close the app. On Mac the app actually
stays open and active in your dock and clicking it
will relaunch your window.
So on Windows and Linux if someone comes the app window you
can safely quit the app as well.
On Mac, however, you need to keep your app running even if
there is no main window. Darwin here is the
internal name for macOS that we can use to match.
Now what you'll need to do is make some changes to the
little comes example we just gave and export
everything to a create window function because we need to
recreate the window when needed. With the
create window function we can safely call that as
soon as the app is ready and what we do here is
add a listener so that when the main window is closed
we also delete it again by setting it to null.
Then when clicking the doc
icon we get the activate event and with this activate
event we can recreate a window again if there is not
currently a window available. So either the window will be created
or we'll just open the existing window.
Now on the web we save user preferences, but all
of them tend to be app specific user preferences.
On the desktop, however, we also have meta user
preferences, and they're not things you might think about because they
don't exist on the web, but they're really important for not frustrating
your user. Two important things in
that regard are remembering window positions and remembering
last opened folders.
Once you start user testing your desktop app,
you'll find out that nearly each user has their own preference for
where your app is on the screen and which dimensions it has.
If a user has to reset that every time they open your app, they are
going to move on to an app that does conform to their user preferences.
So keeping track of your window position is a really
nice thing to do. There are a number of variables you want
to keep track of,
the window dimensions, the position which
screen it's on, and whether or not the app is
maximized. And the last one is actually pretty tricky.
You see, maximized is a state your app can be in,
but if you exit the maximized state, native apps
restore to the previously userset geometry.
So while you should save the fact that your app is maximized,
you shouldn't actually save the geometry for that state.
Building this yourself is not that hard. Electron has events
for the resize and move events, and if
you get the window geometry and save that on each of these events,
as long as it's not in a maximized state and store that,
you can retrieve it again on applaunch and use that.
You can save these settings in a flat file or use something like electron
settings which you can get on NPM.
On applaunch you get the Windows state and test if
it has bounds. It won't have those the first time
someone opens the app. So you do need to provide adequate fallbacks.
Then a gotcha. You can't start a window in
its maximized state. You can only maximize a window after
it's shown. So we check if we should maximize in the
ready to go ready to show event after the window is shown.
But if you want to keep things simple, there is the electron window state
package on NPM that does this work for you.
It will also take care of some uncommon edge cases like resetting
the position if your app was on a screen that's no longer connected to your
computer. The other thing
to keep track of is if your app supports saving
or loading files. You want to keep track of the last use folder
so that every time a user performs an action, they don't have to
drill down from their comes directory. Again,
the nice thing to do here is similar to the window positioning
let users continue where they left off.
If I navigated to a folder to select something, there's a
high chance I want to use that folder again next time I do the same
action like saving or opening a file,
so navigating to that folder saves users a lot
of time. What you want to do as
a developer is on each successful interaction with the file system.
Store the path that the user ended up choosing and
next time for the same interaction, start with that path.
That path has the highest chance of being the correct path,
or at the very least it's better than just opening someone's home directory.
You'll notice most native applications also do this.
Now I mentioned storing this path on successful interactions.
You don't want to store the path if a user ended up canceling the interaction.
Obviously what they wanted to find wasn't on that particular path.
A couple of versions ago, Electron did not ship with the default
application menu, and particularly on macOS. This gave
some issues. If an application doesn't have an application menu
with cut, copy and paste in it, then you can't actually perform those
actions in your app. Guess who found
that out after shipping a note taking app?
Luckily, nowadays Electron will give you a default menu
if you don't set one yourself solving that issue. But the
default menu is pretty Mac centric, and to supply menus
that also make sense on Windows and Linux, where there's a
file menu item instead of the app name and the
help menu generally doesn't have search functionality,
you'll have to replicate the entire menu structure yourself.
To solve this, I made an NPM package called Electron Create Menu.
It replaces the menu API that electron gives you and creates
a platform appropriate menu for you automatically.
It also gives you a new property for each menu item or menu section
that it uses to determine which platform to show things on.
This way, you can have a single menu structure for all three
platforms and still show the appropriate menu items and titles.
This is what it looks like. The first object is
shown on Mac only and the bottom item is hidden on Mac
only, so it's shown on Windows and Linux. And this way you can
very easily differentiate your menu between platforms.
Text highlighting when you press command
a on a website it looks like this,
but if you do the same in say, pages, it's a
little different. The text selection is only contained to
the actual writable area that's currently focused, and none
of the UI or buttons are suddenly highlighted to
get the right effect in electron, where it doesn't mess up
your entire app UI. We need a little CSS to
help out with user select none on the
body. None of your app's UI text will be
selectable. You might think you need to unset this
for input fields, but chromium already takes care of that for us.
Not every application needs a context menu, the menu that shows
when you right click somewhere. But it is something that people
expect particularly in text areas with cut, copy and
paste at least because context menus are
context dependent. Electron doesn't give you one by default,
but it does give you an event you can respond to that lets you create
a context menu yourself and then show it.
It will tell you what the context menu was triggered on so you can show
relevant menu options. If something is editable you
can add but copy and paste and other relevant options.
But if you right click on a link you might also want to show a
copy link location option. You get quite
a bit of information on a right click alongside knowing
if where you right clicked is editable or a link. You can also
get the link text, deselected text or detect if you're
right clicking an image or a video. If you want
to keep things simple there is the electron context menu NPM package
that provides some basics for text, Linux and images
right out of the box.
Different operating systems use different keys for shortcuts
and even though things are converging, it's still something you
need to think about. There is one major
difference where Windows and Linux uses control.
Mac actually uses command which we refer to
as the super or Windows key. On Linux and Windows,
shortcuts in electron are created as global shortcuts which means
they work regardless of your app being focused.
Keyboard shortcuts are written out as a string. So you can
write Alt plus R or backspace and those will work.
But what if you want to add a safe shortcut? Do you add two
control s and a command s?
Electron helps us out here because you can actually type command or control
and electron will pick the right one depending on the platform.
That's going to save you a lot of if statements to
make an app integrate with the system using the same
font as the operating system is a really powerful way
to make it feel cohesive. Unfortunately the browser
default font is not always the system default. Especially when that's
user customizable. So you could
add a huge font stack like this. This one is from
GitHub. Unfortunately, even though it's very long it
doesn't account for the usual Linux system fonts being Ubuntu
Sans oxygen and deja vu Sans. Luckily,
there's a simpler way to do this. System UI
is a special keyword value in CSS that macOS to the font
the operating system uses. It's well supported in
Chromium, and since we know that's what we're running on, we can use this without
a fallback. So this will automatically pick Ubuntu Sans
on Ubuntu, Segway UI on Windows, and can
francisco on Mac. You can
also, of course, use your own fonts like slack comes with the font
leto. In this case, it's best to ship the font along
with your application, either as a wolf two or a TTF.
So that's my eight tips and I'll review them in a second. But I can't
give a talk about electron without mentioning memory now.
Personally, I think this is not the huge problem everyone pretends
it is. Sure, your average vim user is going to
bark at using 100 megabytes base memory just to run the
app, but really, it's not significantly higher than most other GUI
heavy apps. The problem you can run into though,
is memory leaks. If you come from the
web, you really only need to care about the worst of memory leaks.
The runtime of a single page tends to be relatively short.
This changes a little if you're working on spas, but even
those tend to throw in a full page refresh every now and then.
Apps are much longer lived, and because of that, small memory leaks
can also become issues. So let's check out some strategies
in dealing with these. Before I start,
I want to mention that Electron has excellent performance documentation at
Electron app Docs tutorial performance
that focuses on making sure your app starts fast and that actions
feel snappy. Things like bundling your code only,
shipping the polyfills you need, and loading code strategically,
it's an excellent resource, and it's definitely worth checking out.
Now, for memory leaks, we get the benefit of Chromium
and its developer tools. In recent years, the developer
tools have added really good performance tooling, and you
can use that right in electron to suss out any memory issues in
your application. You can start a recording in this screen
making sure that memory is checked, and use your app or the functions
in your app that you want to test. When you're done,
you end up with this view. It's a little intimidating
and too much to go in through for this presentation,
but what you want to focus on is this graph. It shows the
memory usage and the number of listeners.
If both of these go up and up and up.
There's probably a listener you're not clearing somewhere.
Additionally, in the flame graph above the chart,
you can see some function calls that take too long, recognizable by the
red ranked angle. These would be calls to look into
to see what is causing the slowdown.
Now for the note part of Electron, you can also use the
chrome devtools in the same way. If you start electron with the
inspect flag, then open chromium and go to chrome
inspect and pick your application from the list.
So to conclude, building a crossplatform app
that feels great everywhere doesn't require conforming to the platform UI
100%. Thanks to web apps and mobile devices,
people are more familiar with different interfaces, but the devil
is in the details to make your app feel at home,
take care of at least these things. Don't launch
your app like a web page, but hide it until the page is loaded and
give it the right background color.
Follow the platform's way of handling apps and windows.
Keep the app running on macOS.
Remember user preferences like Window Geometry and the
last opened folder give users
the menu, titles and actions they expect from their platform.
Prevent users from selecting UI text and
make sure you have a context menu where users expect one.
Use the right keyboard, shortcuts command for Mac and control
for Windows and Linux.
Use the system font as a way to make your app feel part of the
platform and lastly, keep your memory leaks under control.
These are all the ingredients to make app field at home on all
platforms. Check out electronjs.org
for more information on electron. My name is Kilian
Valkhof and you can find me at these links.