I started noticing something odd in my Google Search Console statistics for this site about two weeks ago:

The prior drop, in mid March, is most likely due to the onset of the COVID-19 pandemic.

Around May 4th of this year, my results started tanking, anywhere from 600 less clicks to a massive 1400 less clicks per day! That's a lot of traffic that was suddenly missing, traffic that I didn't know how to get back.

This was, obviously, worrying. I'd had drops in site traffic before, but none were this pronounced or this sudden. I decided to take a closer look at my stats from Analytics and Search Console, and I did not like what I found.

Every single post on Exception Not Found, from the earliest one to the most recently public series, was now getting less search traffic. Markedly less. So if every single page was showing less organic search traffic, then there must be something site wide going on.

I started searching for answers. Many articles I read pointed out that I should check if I had any "manual actions" or "security issues" on my site, and resolve them. I found none. They suggested that I look at my keywords to see if they changed, to check the design to see if it was making the search crawler's job harder, to see if inbound links to my site had disappeared, and even if the content itself to was not actually as useful as I thought it was. I found no outstanding issues, nothing that I could find to explain the large drop in organic search traffic.

And then I stumbled on this announcement:

Later today, we are releasing a broad core algorithm update, as we do several times per year. It is called the May 2020 Core Update. Our guidance about such updates remains as we’ve covered before. Please see this blog post for more about that:https://t.co/e5ZQUAlt0G

There’s nothing wrong with pages that may perform less well in a core update. They haven’t violated our webmaster guidelines nor been subjected to a manual or algorithmic action, as can happen to pages that do violate those guidelines. In fact, there’s nothing in a core update that targets specific pages or sites. Instead, the changes are about improving how our systems assess content overall. These changes may cause some pages that were previously under-rewarded to do better.

This is supposed to make me, a small site owner heavily dependent on Google search results for traffic, feel better. But that's hard to do when 20%-30% of my traffic is gone almost overnight, and it was most likely because of Google's own changes. If I sound bitter, well, frankly I am; I worked hard on those posts and they were useful to someone, and now because of an algorithm change they're not as useful anymore?

From 5015 pageviews on Monday May 4th to 3850 pageviews on Monday May 11.

There's no two ways about it, Google wields a LOT of power when it comes to the results of their search algorithm. I'd argue they wield more power than any single other company on the Internet, merely by having the best search engine available (and make no mistake, it is the best).

It does seem, if not quite unfair, at least a little unbalanced that Google wields so much influence into who sees what on the internet. If you run a website, no matter what it is, you are at the mercy of your Google overlords. Google is the 2000-pound gorilla, the target against which all metrics are compared, the source and arbiter of eyes on most websites.

They hold the power, and it cuts both ways; they can both raise your profile to unheard of heights and sink you so low that no one will ever find you, namely, on the second page of results. That's a lot of power for a single organization, not to mention a business that ultimately wants to make money, to wield.

I am not sure how I will recover from this drop yet. Or, frankly, if I need to worry about it at all. I'm clearly not gonna stop blogging anytime soon. But Google has not stopped being, as Jeff Atwood put it 11 years ago, the elephant in the room. If anything, the elephant has only gotten larger, and hungrier. Here's hoping that, should we need to, we can get out of its way in time.

The Final DrawingEditor Modifications

Our save function will need to exist on the Razor Page code-behind file. Because of this, we require a way for our main DrawingEditor class to use that function as a callback. We also need to be able to create the JSON for the current drawing, and lucky for us, FabricJS provides a way to do exactly that:

Note the saveDrawing() function; this is not implemented quite yet. The next thing we need is to make the page handler that will "process" the POSTed drawing JSON. Since we're not actually doing anything with the JSON, the code is very short:

In a production system, we'd need to persist the drawing JSON to a datastore or database.

In normal circumstances, we'd almost certainly want the OnPostSave() method to return something, be it IActionResult, JsonResult, or just a string.

The Final Script

Now we need to return to the Razor Page and implement the saveDrawing() function to call the new OnPostSave() method. We also need to show and hide the "saving" status message on the status bar immediately below the canvas.

One additional thing Chris decided to do was put some extra code in to prevent "double-click" saving, so our save method can only be called one second after it was last called. You can see all of this code below:

The Last Hotkey

There's one last little thing we should do: since we already have hotkeys for cut, copy, paste, etc. we should implement the hotkey for save (Ctrl+S) as well. Here's the modifications we need to make to DrawingEditor to realize this:

GIF Time!

Let's see how this works in a GIF:

Pay special attention to the lower-right side of the GIF, where the "Saving" message will appear very briefly.

Conclusion

That's it! With this last feature, we have made our drawing canvas match the goal photo from the beginning of the series! Thank you, dear readers, for reading these posts about creating a drawing canvas with FabricJS and TypeScript! Chris and I are pretty proud of this sample, and we hope it helps you all in your projects.

For the final time, don't forget to check out the sample project for this series over on GitHub. It contains the final, working code for this series. Don't be afraid to make pull requests if you see a way we can improve!

We're nearing the end of our Drawing with FabricJS and TypeScript series. In this part, let's add one last set of improvements by implementing cut, copy, and paste functionality as well as hotkey shortcuts for those, plus undo and redo.

We need to fill in each of the methods (other than the constructor, which for this class doesn't have an implementation; it just gets an instance of DrawingEditor).

Let's start with copy(). When we copy an object, we place a copy of it in memory so that it can be pasted later. In this particular case, we need to get the active object on the FabricJS canvas, clone it, and then set it to the memorizedObject property of Copier. Plus, we have an optional callback method that needs to be executed, if it is not undefined. Here's how that looks:

The callback method becomes immediately useful, because we now need to implement the cut() method. Cut is really just copy and then delete, so our cut() method will pass a function that deletes the selected object after it is cut. Here's the implementation:

cut() {
this.copy(() => this.editor.deleteSelected());
}

The last part of this is the paste() method. In this method, we clone the memorized object, add it to the canvas, and re-render the canvas. Here's the code:

But wait! There's currently no way the user can actually use this functionality! For cut/copy/paste, we aren't going to add display components to the toolbar. Instead, we're going to enable hotkeys (e.g. Ctrl+X, Ctrl+C, Ctrl+V).

Hotkeys

Not only will we enable hotkeys for cut, copy, and paste, we'll also enable them for undo (Ctrl+Z) and redo (Ctrl+Y). To start, we need our DrawingEditor class to be aware of the key codes for each of the letters. One of the ways we can do this is by using a very neat site called keycode.info. Here's the keycodes for X, C, V, Z, and Y:

TypeScript defines for us a KeyboardEvent class which represents an event where the user pressed a key on the keyboard. That event also tells us if the Ctrl key was pressed during the event. We can use this class to initialize our key code events:

That's all we need to do for hotkeys! Now the user can cut, copy, paste, undo, and redo using the keyboard shortcuts for those events! This is difficult to put into a GIF (because you wouldn't be able to see what keyboard shortcut I was using), so there won't be one for this part of the series.

In this part, we're going to further improve our drawing canvas by adding an undo/redo functionality. To accomplish this, we have to implement a state manager system which keeps track of the various states of the canvas and the objects on it. It's not as bad as it sounds, I promise. Let's go!

The State Manager

In order to accomplish this undo/redo functionality, we need a class that will keep track of the various states of the drawing canvas. Said class will need to keep a representation of the canvas in JSON, so that said representation can be easily restored.

Lucky for us, FabricJS already provides a way to get the JSON for the canvas: the method toDatalessJSON(). By using this method, we can get the complete state of the canvas at any given time.

The state manager itself will need to keep a stack of states, so that we can pop off the top state to undo. It will also need to keep a separate stack of popped states, so that we can redo.

The method saveState() is used as a shortcut method to allow other methods to a) save the current state and b) render all objects on the canvas again. saveState() needs to be used in quite a few places, most notably whenever a canvas object is modified, created, or deleted.

class DrawingEditor {
//...Properties and constructor
private initializeCanvasEvents() {
//...Other events
this.canvas.on('mouse:up', (o) => {
this.isDown = false;
switch (this.cursorMode) {
//If the cursor mode is currently Draw when a mouseup
//event occurs, we have just finished dragging to
//create that object. Hence, we need to add the new
//state of the canvas to the state manager.
case CursorMode.Draw:
this.isObjectSelected = false;
this.saveState();
}
});
//If an object has been modified at all, save the new state
this.canvas.on("object:modified", (e) => {
this.saveState();
});
}
//This method is called by the DeleteComponent from Part 5.
deleteSelected(): void {
this.canvas.remove(this.canvas.getActiveObject());
this.canvas.renderAll();
this.saveState();
}
//...Other methods
}

Our DrawingEditor will now save the state of the canvas whenever objects are changed, created, or deleted. But we still need toolbar items for undo/redo. Guess what that means? We need some new display components!

Creating the Custom Image Dropdown

Drawing is an inherently visual endeavor, and due to this Chris felt that we needed an appropriately visual selector for the colors, line styles, and fills. Hence he created something he termed an Image Dropdown, a class which shows you little images instead of text for each selectable object, and can be used for multiple types of selections.

But before we can use it, we have to make it. And we need some basic stuff to start. Basic stuff like a class to represent each option in the Image Dropdown...

class ImageOption {
display: string;
value: any;
text: string;
}

...a class to represent the options we need to select to render the image dropdown itself...

But now we come to the most difficult portion of this implementation: the attachEvents() method.

attachEvents() Method

Whenever an element in the Image Dropdown is clicked, we need to ensure that the next objects which will be drawn in the canvas are drawn with the selected line color, fill color, or style. Plus, the item that has been selected must appear in the "viewable" portion of the display, just like in a standard drop down.

With that in mind, let's see the annotated code for the attachEvents() method.

Most of this code probably doesn't make much sense yet, but I promise it will.

We have now fully written our reusable ImageDropdown class! Time to implement a few instances of it.

Line Color and Fill Color

We can demonstrate how to use our new ImageDropdown class by implementing a way for the user to select the line color and fill color for the lines, shapes, and text they want to draw.

We're making a couple assumptions about this element:

There will only be a certain number of colors available. The user won't pick from a color wheel (though this would be an excellent improvement if you, dear reader, want to make a pull request for it) AND

The same set of colors will be available for lines and fill color.

Let's build a reusable display component that will make use of ImageDropdown and let the users choose colors! Here's the code for our component.

NOTE: In the above code and in many other places throughout this series, you may see constructors that have readonly parameters. In TypeScript, this is the same as instantiating a property for the class that contains that constructor, and so we don't need to define a property. I did not know this before starting this series, and I can see how it's a useful feature to have. See the docs for more info.

Just like our earlier display components, we need to modify the base DrawingEditor class and the Razor Page markup and script to wire up the new ColorChooserComponent. But this time, there's some extra changes that also need to be made.

In DrawingEditor, because it is that class which is aware of the brush currently being used to draw objects, we need to modify that brush whenever a new fill color or line color is selected. This is in addition to adding the new line/fill components to the addComponents() method, like we did for the other ones. Here's the changes to DrawingEditor for both of these:

GIF Time 1!

Now we can finally run the app, and use our new line color and fill color pickers! Here's a GIF that shows these features in action:

But wait, we're not done yet! Now let's see how to change the line styles and width.

Line Styles

Our requirements for this project specified that we needed a way to change the line "style" of the shapes. That is, we needed to allow the user to draw dotted, dashed, or solid lines. So, let's build a component to do this!

In FabricJS, there is a class called BaseBrush, which represents the brush being used at any given time to draw objects. BaseBrush contains two properties we will need to use: strokeLineCap and strokeDashArray.

strokeLineCap sets the line ending of the line being drawn; in our case, we only use the value "round".

strokeDashArray sets the properties of the dashes being drawn as part of a line. This allows us to set the size of the dashes. Our requirements specify that we need both dotted and dashed lines, so we will use strokeDashArray to create each of these.

GIF Time 2!

Guess what? It's GIF time again! Let's see how the new LineTypeComponent looks:

Woohoo! Almost there! There's only one thing left we need to do, and that's to create a component which allows the user to choose the thickness of the line being drawn.

Line Thickness

Given that you, dear reader, have probably seen this pattern many times by this point, I'm going to give you the short version of implementing this component. First, we need the new component class, LineThicknessComponent:

Implementing the DeleteComponent

We're going to implement this delete functionality a bit backwards from the way we've implement other functionality: we're going to write the component first, then integrate it with the drawing editor.

Our component will exist in the toolbar, but needs to only be active when an object is selected; otherwise it should be disabled. Further, this isn't going to be the only component that changes the canvas: later in this series we're going to implement undo/redo functionality. So, we need a "base" class for all components that will do non-drawing functionality.

Chris termed these components ControlComponents, and implemented the following abstract class:

Modifying the Markup and Script

There's something little bit strange about FabricJS's Canvas object: it doesn't provide events for keydown or keypress, so we have to wire those events up against the page, and then call the appropriate methods in the DrawingEditor class.

Here's the markup changes and script changes we need to make in our Razor Page. Note that the delete button will sit by itself on the leftmost side of the toolbar:

With all of these changes in place, we can now write text onto our canvas, as shown in this GIF:

Note that our implementation did not require us to change things like the text font, size, style, or color, and so I leave those kinds of improvements up to you, my dear readers.

Let's also take a few minutes to implement another useful FabricJS feature: polylines.

Freeform Lines (AKA Polylines)

"Freeform" lines are lines which are drawn freely onto the canvas: think using the pencil tool in Paint. FabricJS terms these "polylines" because in reality a "freeform" line consists of many tiny straight lines that combine to form what looks like curves. These straight lines are created by storing a list of ordinal points, between which lines are connected (essentially like playing a giant version of connect-the-dots).

As with Text, we need two parts: a drawer and a display component. Here's the drawer class:

GIF Time!

All of this together allows us to draw freeform lines, as shown in the below GIF:

Ta-da! Now we have some very useful tools added to our FabricJS canvas toolbox!

Summary

Just like with the basic shapes, for both Text and Polylines we needed to implement a drawer and a display component. Those classes then needed to be wired up to the main DrawingEditor class, and into the Razor Page markup and script.

Initializing the Display Components

Before we can build more shape drawers, we first need a way to display the existing drawers in a "toolbar" so the user can select which one they want. We need a lot of pieces to make this work, so let's get started building them.

The first thing we need is a class that represents each displayed option. We are calling these classes "display components" and we need a base one from which all specific components can inherit. This class will need some properties that allow us to set up things like the CSS classes and icon displayed, as well as set the Drawing Mode.

Here's the annotated code for this base class, called DisplayComponent:

This class implements an SVG definition for the component's icon, and saves the alt text, before using super() to call the parent class DisplayComponent's constructor.

Showing the Display Components

We're halfway to being able to, ahem, display the display components. Now we need to make some changes to the main DrawingEditor class.

We want our DrawingEditor (which, as a reminder, represents the drawing area as a whole, including our display components) to be able to initialize components at creation time. To do this, we're going to create two methods:

The method componentSelected() needs to be implemented on DrawingEditor. We do that like this:

class DrawingEditor {
//...Properties and Methods
componentSelected(componentName: string) {
//Deselect any objects on the canvas that are selected
this.canvas.discardActiveObject();
//FOREACH component in the drawing editor...
for (var key in this.components) {
// IF this component has a property with the passed-in name
// THEN do nothing
if (!this.components.hasOwnProperty(key)) continue;
//OTHERWISE...
const obj = this.components[key];
//IF the component with the passed-in name
//IS the component we expect
if (obj[0].target === componentName) {
//SET the drawing mode to the drawing mode
//needed by the component
this.drawingMode = obj[0].drawingMode;
}
//IF the method selectedChanged is defined on the component,
//THEN call that method
if (obj[0].selectedChanged !== undefined) {
obj[0].selectedChanged(componentName);
}
}
}
}

HTML and Script

We have one last piece we need to do to set up our first display component: we need to insert some HTML and some JavaScript on our Razor Page where we want to make a drawing.

Next up in this series, we'll make a new component that will allow us to delete objects from the canvas, and implement hotkey functionality for the same. Check out Part 3 of Drawing with FabricJS and TypeScript!