Link/1 action on mousedown instead of click

Would there be interest to implement a link/1 that gives to option to operate on other events?

So I implemented a simple link/1 function that instead of working on click works on mousedown (or whatever you want, based on a hook).

Just took a look at Lieview code, copied the link/1 function, and added a hook onmount that calls click which, based on also Lieview code, already has an event and event checks for if(this.pendingLink === href) {return;} so I don’t need to prevent the next click event after the mouse is lifted. Works really well.

  attr :id, :string, required: true
  attr :navigate, :string
  attr :patch, :string
  attr :href, :any
  attr :replace, :boolean, default: false
  attr :method, :string, default: "get"
  attr :csrf_token, :any, default: true
  attr :rest, :global, include: ~w(download hreflang referrerpolicy rel target type)
  slot :inner_block, required: true

  def fast_link(%{navigate: to} = assigns) when is_binary(to) do
    ~H"""
    <a
      id={@id}
      href={@navigate}
      data-phx-link="redirect"
      data-phx-link-state={if @replace, do: "replace", else: "push"}
      phx-hook="FastLink"
      {@rest}
    >
      <%= render_slot(@inner_block) %>
    </a>
    """
  end

Repeat the others for push and href.

The hook is just:

export const FastLink = {
  mounted() {
    const linkEl = this.el;

    linkEl.addEventListener("mousedown", (e) => {
      e.preventDefault();
      e.stopImmediatePropagation();
      linkEl.click();
    });
  },
};

2 Likes

Also, new bindings such as phx-mousedown would help as well. Again not sure if this would be something of interest.

My JS is rusty, but wouldn’t you want pointerdown instead so that touchscreen devices are supported as well?

Maybe, will research more and report back. I also need to account for only left button pressed, mousedown also works on any mouse button.

I have written additional bindings to replace phx-click and so on, I also need to change behavior of forms.

Definetely prefer this behavior. Improves latency significantly. May roll it on other non-elixir projects as well.

I have read some concerns about accessibility, regarding pointer cancellation. I don’t buy, there’s plenty of options to go back or undo actions. Can still use normal click on important actions or ask for confirmation. Understanding Success Criterion 2.5.2: Pointer Cancellation | WAI | W3C

I always like it when people experiment with unofficial solutions. But….

You might want to buy a bit more from people who actually studied usability patterns. For me: If I touch a button and it fires before I lift my finger…I get really irritated. Maybe I want to open the link in another tab (I can’t), select the text (I can’t) and other issues you have probably never thought of as you do not know all use cases. You as developer making the decision if it should be a fast link or a normal one is killing (_blank and _new are considered bad practice too)

There are usability guidelines for a reason; so you don’t have to learn all reasons why we came up with them in the first place.

6 Likes

Another example: some developers think it is nice to auto redirect users to localized pages based on their location. It sounds so convenient! And it reduces the user journey with a whole click!

But now I am on vacation and the darn website won’t let me go beyond the French section…

Sometimes ‘faster’ ain’t better.

1 Like

Agreed and was having the same thoughts. Mousedown definitely has its applications (like in games) but a regular site that did this I wouldn’t use. I certainly use pointer cancellation. There’s also dragging links to the bookmarks bar and into another window.

And now I read the opening post again

on other events

So probably not for navigation…mixed this thread up with a discussion to make it the default.

I also read not carefully enough :smiling_face_with_tear:

1 Like

I think that with a mouse, you can totally implement it, as long as it fires only on left click, leaving right click or wheel to do the other operations you mention. Touchscreen definitely requires standard behavior and should not fire on initial touch. I think “mousedown” actually behaves like this. “pointerdown” does not. Will do more testing and see if it makes sense, haven’t worked on this since yesterday.

As @sodapopcan mentions though, would not allow dragging or any cancellation, would have to make an additional action to undo, so a tradeoff there.

For language I am totally with you, I just read the headers to set a default, and avoid mixing languages and regions.

To be clear, I am thinking of doing any action, including navigation, on mousedown.

Lastly, even though this is my initial idea, the post is kind off asking why/if would make sense to add more events (all?) to bindings and allow other events to link as well. Although seeing your inputs, the latter seems to not make less sense.

If you want a link to behave like a link, please don’t do this.

If you want to use a link for other purposes then sure, but please don’t do this to actual hrefs. As others have stated, the loss in consistency, predictability, and accessibility isn’t worth it.

2 Likes

Well you need to break standards to push things forward and when you do whatever it is that you changed is not going to behave like the previous thing.

Also unsure why such strong opinions on this matter, specially against. I think it is more of a preference.

Aside from this, I think it is interesting to discuss if more events should be added, specially to bindings, since the link thing is clearly too non-standard and from what I understand from the Liveview codebase would not be such an easy change.

My ‘opinion’ is so strong because I actually studied usability. So I do not guess, I know. And I share that information with you.

Your logic is broken. Pushing things forward might be accomplished by breaking the rules. That does not mean the other way around is also true. I could, for example, break the rules of this forum by using CAPS-LOCK ALL THE TIME. BUT WE BOTH AGREE IT DOES NOT PUSH ANYTHING, EXCEPT THE WRONG BUTTONS (pun intended).

You asked for opinions; you got opinions. Don’t act like all who replied are narrow sighted and suggest they don’t like going forward.

2 Likes

And you can find lots of usability test results that prove you are wrong. So I think you should respect the work of others and proof why your variant is better, instead of the other way around.

I’m sorry but I disagree with your views on this topic and still think mousedown is in no way an issue for accessibility and I was not able to find convincing information that suggested otherwise.

Mousedown provides ~100ms latency improvement and its use is justified, specially on applications that are already optimized for low latency. Simply switching to mousedown could mean from a 20-95% latency improvement on actions.

1 Like

For anyone interested in implemented this, these are the areas that need work:

-For links, check OP, works really well, no issues.

-For general actions, since phx-mousedown is not provided by default, we need to create custom bindings. Check following guide on how to do it: Custom client-side bindings for Phoenix LiveView | BetterDoc Product Development Blog

-When using custom bindings that point to a handle_event on the server, you need to manually write the JS push event, instead of just a string with the name of the server event. The following will call the server handle_event decrease_quantity:

<button
            class="btn btn-square btn-xs phx-submit-loading:opacity-75"
            qphx-mousedown={JS.push("decrease_quantity")}
            phx-value-itemid={@smth.id}
          >

-Form forms, I wrote a custom hook for the Button that executes the form submit:

The Hook

export const FormSubmitOnMousedown = {
  mounted() {
    this.el.addEventListener("mousedown", (e) => {
      if (e.button !== 0) return;
      e.preventDefault();
      this.el.closest("form").requestSubmit();
    });
    this.el.addEventListener("click", (e) => {
      e.preventDefault();
      e.stopImmediatePropagation();
    });
  },
};

And then the Form and Button:

<.form
      for={@form}
      phx-change="validate"
      phx-submit="save"
      autocomplete="off"
      class="grid grid-cols-1 lg:grid-cols-2 gap-10 lg:gap-14 border rounded-lg shadow p-4 md:p-12 bg-base-100"
    >
<%!-- ALL YOUR FORM STUFF --%>
          <button
            id="88f27b4f-1b0a-4854-9651-843130e40f82"
            class="btn btn-block btn-primary btn-lg phx-submit-loading:opacity-75"
            phx-hook="FormSubmitOnMousedown"
          >
            <.icon name="hero-plus" class="h-6 w-6" /> <%= gettext("Do Something") %>
          </button>
</.form>

And that should be all. I think we should consider adding more native bindings, including phx-mousedown and an opt-in for .link navigation.

We have seen Nextjs recently moving in this direction, as seen by Vercel CTO on this post:
https://x.com/cramforce/status/1848731869342535934

I’m also curious on exploring aggressive route prefetching with a client router, similar to what Nextjs client router allows, although this option will have significant downsides.

Just to be clear: you did find the information but you were still not convinced? Ah well…you do you as they say :upside_down_face:

1 Like