If you have any thoughts on my blog or articles and you want to let me know, you can either post a comment below(public) or tell me via this i_kkkp@163.com

Browser Default Behaviors

Preface

In fact, browsers come with many built-in events, and many events automatically trigger certain behaviors in the browser.

For example:

  • Clicking a link triggers navigation to the URL.
  • Clicking the submit button in a form triggers submission to the server.
  • Pressing and dragging the mouse button on text selects the text.

When handling an event with JavaScript, we usually don’t want the corresponding browser behavior to occur. Instead, we want to implement alternative behaviors.

Preventing Browser Behavior

There are two ways to tell the browser that we don’t want it to execute default behavior:

  • The common way is to use the event object, which has a event.preventDefault() method.
  • If the handler is assigned using on<event> (rather than addEventListener), returning false is also effective.

In the example below, clicking the link will not trigger navigation, and the browser will not perform any action:

<a href="/" onclick="return false">Click here</a>
or
<a href="/" onclick="event.preventDefault()">here</a>

Click here
or
here


It’s important to note that using on<event> and returning false is not a good practice.

Returning false from the handler is an exception. The return value of event handlers is usually ignored. The only exception is returning false from a handler assigned using on<event>.

Handler Option “passive”

The optional passive: true option in addEventListener signals to the browser that the handler will not call preventDefault().

Why is this necessary?

On mobile devices, some events like touchmove (when the user moves their finger on the screen) can lead to scrolling by default, which can be prevented using preventDefault() in the handler.

So, when the browser detects such events, it must first process all handlers. If preventDefault is not called anywhere, the page can continue scrolling. However, this may cause unnecessary delays and “jitter” in the UI.

The passive: true option informs the browser that the handler will not cancel scrolling. The browser then immediately scrolls the page to provide a smoother experience and somehow processes the event.

For certain browsers (such as Firefox and Chrome), touchstart and touchmove events have passive set to true by default.

event.defaultPrevented

If the default behavior is prevented, the event.defaultPrevented property is true; otherwise, it is false.

Here’s an interesting use case.

Do you remember our discussion on event.stopPropagation() in the Bubbling and Capturing chapter and why stopping propagation is not good?

Sometimes, we can use event.defaultPrevented as an alternative to notify other event handlers that the event has been handled.

Let’s look at a practical example.

By default, the browser displays a context menu with standard options on the contextmenu event (right-click). We can prevent it and display our custom menu like this:

<button>Right-click shows browser context menu</button>

<button oncontextmenu="alert('Draw our menu'); return false">
  Right-click shows our context menu
</button>


Now, in addition to this context menu, let’s implement a document-wide context menu.

When right-clicking, it should display the nearest context menu:

<p>Right-click here for the document context menu</p>
<button id="elem">Right-click here for the button context menu</button>

<script>
  elem.oncontextmenu = function(event) {
    event.preventDefault();
    alert("Button context menu");
  };

  document.oncontextmenu = function(event) {
    event.preventDefault();
    alert("Document context menu");
  };
</script>

Right-click here for the document context menu


Issue and Solutions

The problem arises when clicking on elem, and we get two menus: the button-level menu and the document-level menu due to event bubbling.

One solution is to prevent the event from bubbling up when handling the right-click event in the button. We can achieve this using event.stopPropagation():

<p>Right-click for the document menu</p>
<button id="elem">Right-click for the button menu (fixed with event.stopPropagation)</button>

<script>
  elem.oncontextmenu = function(event) {
    event.preventDefault();
    event.stopPropagation();
    alert("Button context menu");
  };

  document.oncontextmenu = function(event) {
    event.preventDefault();
    alert("Document context menu");
  };
</script>

Now, the button-level menu works as expected. However, this comes at a cost — we deny any external code access to right-click information, including counters for collecting statistics. This is not advisable.

Another alternative is to check whether the document handler has prevented the browser’s default behavior. If it has, the event has already been handled, and we don’t need to react to it:

<p>Right-click for the document menu (added a check for event.defaultPrevented)</p>
<button id="elem">Right-click for the button menu</button>

<script>
  elem.oncontextmenu = function(event) {
    event.preventDefault();
    alert("Button context menu");
  };

  document.oncontextmenu = function(event) {
    if (event.defaultPrevented) return;

    event.preventDefault();
    alert("Document context menu");
  };
</script>

Now everything works as expected. If we have nested elements, each with its own context menu, this approach will work. Just make sure to check event.defaultPrevented in each contextmenu handler.

event.stopPropagation() and event.preventDefault()
As we’ve seen, event.stopPropagation() and event.preventDefault() (also considered as return false) are two different things. They are unrelated to each other.

Nested Context Menu Structure
There are other ways to implement nested context menus. One approach is to have a global object with a document.oncontextmenu handler and a method to store other handlers. This object would capture any right-click, browse stored handlers, and run the appropriate one.

However, every piece of code needing a context menu should be aware of this object and use its assistance rather than having its own contextmenu handler.

Summary

There are many default browser behaviors:

  • mousedown — Starts selection (dragging the mouse for selection).
  • click on <input type="checkbox"> — Selects/deselects the input.
  • submit — Clicking <input type="submit"> or pressing Enter in form fields triggers this event, leading the browser to submit the form.
  • keydown — Pressing a key adds a character to a field or triggers other actions.
  • contextmenu — Event occurs on right-click, triggering the default behavior of showing the browser context menu.
  • …and many more…

If we want to handle events using JavaScript only, all default behaviors can be prevented.

To prevent default behavior, you can use event.preventDefault() or return false. The second method is only applicable to handlers assigned through on<event>.

The passive: true option of addEventListener informs the browser that the behavior will not be prevented. This is useful for certain mobile events (like touchstart and touchmove) to let the browser scroll without waiting for all handlers to finish.

If the default behavior is prevented, the value of event.defaultPrevented becomes true; otherwise, it is false.

Implementation Principles of Vue.js Components Browser Event Delegation

Comments