When you are looking to quickly edit a file, chances are you open something like Notepad or Notepad++. However, on more limiting devices such as a phone or a Chromebook with no built-in text editor (Google Docs is a word processor, so it doesn’t count), your best bet is searching something for an online text editor. The issue with this is that most online text editors are sloppy and don’t get the same features as you do on desktop (and the ones that do feel very overwhelming)
The Idea
Here, I will be refreshing my memory on the three web technologies by creating a text editor that is reminiscent of Vim and Emacs. It should have a tab bar like Emacs, the commands of Vim, and full keyboard support like both. Supporting Vim Script or the macros that gives Emacs its name is out of the scope of this guide, especially for an online text editor. However, they may be supported in a future update.
I will also be using pure HTML, CSS, and JS. No preprocessors or frameworks used here. This is because I am documenting my learning process here for those three technologies, not for external UI frameworks.
One important thing to note is that this text editor will only have two modes, “insert” and “view,” and the default mode will be insert. This is because while I do want to make sure my editor is powerful and familiar to Vim and Emacs users alike, it should also be user-friendly. In insert mode, you can edit the text directly, but if you press ESC and end up in viewing mode, you can use Vim-like commands and control the top menu bar with your keyboard, like you can on Emacs.
Starting Off
I started off with a single file, index.html
, where I put in the standard boilerplate and then set the title of the webpage to what this text editor will be named: FeatherText.
Then I linked a CSS file called style.css
.
<!-- Place within <head> tags -->
<link rel="stylesheet" href="style.css">
Within that style.css
file, I then started by using CSS variables to define a starting theme (did I mention that this text editor will have multiple themes?)
.dark {
--window-bg-color: #010022;
--text-color: white;
}
Then, I had to link the theme to the webpage by adding the dark
class to the body
element. This way, the dark theme will be enabled by default.
<!-- Replace "<body>" with the text below -->
<body class="dark">
Afterwards, I removed the scrollbars (since they were just going to cause problems down the line) and applied the dark theme to the webpage by setting the background color and text color to match with the one defined in the theme.
body {
background-color: var(--window-bg-color);
color: var(--text-color);
height: 100vh;
width: 100vw;
margin: 0;
overflow: hidden;
}
Then, I added two fonts to be applied to the entire webpage in different UI elements.
/* PUT THESE LINES AT THE VERY TOP OF STYLE.CSS */
/* Import "Azeret Mono" font (to be used on UI elements like the menu bar) */
@import url('https://fonts.googleapis.com/css2?family=Azeret+Mono:ital,wght@0,100..900;1,100..900&display=swap');
/* Import "DM Mono" font (to be used in the text box) */
@import url('https://fonts.googleapis.com/css2?family=DM+Mono:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap');
Top Menu Bar
To implement a top menu bar, similar to the one you get in Emacs, I started off with a div
full of other div
s (this time our menu items), so our HTML file would look something like this:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style.css">
<title>FeatherText</title>
</head>
<body class="dark">
<div id="top_menu_container" class="fullwidth flex_container">
<div class="top_menu_item">File</div>
<div class="top_menu_item">Edit</div>
<div class="top_menu_item">Go To</div>
<div class="top_menu_item">Cursor</div>
<div class="top_menu_item">View</div>
<div class="top_menu_item">Options</div>
<div class="top_menu_item">Help</div>
</div>
</body>
</html>
Let’s give this top menu bar some padding:
#top_menu_container {
padding: 2px;
flex-wrap: wrap;
}
Now, let’s give each item (except for the first) a minimum of 23 pixels of left margin, but preferably 1.5% of the total width of the screen. Let’s give our first item 10 pixels of left margin.
#top_menu_container *:not(#top_menu_container *:first-child) {
margin-left: clamp(23px, 1.5vw, 100vw);
}
#top_menu_container:first-child {
margin-left: 10px;
}
Let’s give every item a top margin of 10 pixels and clear out any other default margins set by the browser, while making sure that our cursor remains the default (this is just in case we decide to add a link to the top menu bar, which would change the cursor to a pointer).
#top_menu_container * {
margin: 0;
margin-top: 10px;
}
.top_menu_item {
cursor: default;
}
Then, let’s add a border at the bottom on hover for every top menu item to give indication of what is being hovered on.
#top_menu_container div.top_menu_item {
transition: border-bottom linear 50ms;
}
#top_menu_container div.top_menu_item:hover {
border-bottom: 4px solid var(--text-color);
}
But wait! You may notice a problem with what I have done. While nothing seems wrong, try putting some sample text below the menu bar and see what happens below.
You probably noticed that the bottom border transition shifted the text downwards, which I would say is pretty distracting and does not look good at all. This is because the border-bottom
property affects the size of the element. There are some ways to get the same effect without affecting layout, but the simplest one to explain (and the one I am using here) is to keep the border on all the time but only show it on hover.
The clip-path
property essentially allows you to crop an element to a specific viewable portion. Better yet – you can animate it.
What we will be doing is keeping the border always on but set the clip-path
property such that the border is hidden. Then, we can animate the clip-path
property to show the full shape on hover. Let’s replace our CSS rulesets to use this approach.
#top_menu_container div.top_menu_item {
clip-path: polygon(
/* Top left corner */ 0 0,
/* Top right corner */ 100% 0,
/* Bottom right corner */ 100% calc(100% - 4px),
/* Bottom left corner */ 0 calc(100% - 4px) /* Element full width, minus the border width*/
);
transition: clip-path 50ms linear; /* Animate it */
border-bottom: 4px solid var(--text-color);
}
You will notice that the first number of every pair passed into the polygon
function is 0, except for the top and bottom right corners, which are 100%. This represents the x-index for our polygon, but because we are not cropping anything from the sides, this can represent the full width.
And then on hover, we show the full shape to reveal the border.
#top_menu_container div.top_menu_item:hover {
clip-path: polygon(
0 0,
100% 0,
100% 100%,
0 100%
);
}
Coding the Menu Options
These menu buttons are cool, but they don’t do anything yet. So, I created a folder called scripts
and a file in that folder called top_menu_bar_options.js
.
Right now, we have the basic look and feel of a traditional menu bar app, but to make it functional, we will need to use some JavaScript. Stay tuned for a part 2!
Leave a Reply