Lesson 39 of 45 · Web pages

Mouse events

A page that only shows things is a document. A page that answers a click is a program. This lesson shows how to attach a Salam function to a mouse event, how to read where the pointer is, and how the twelve mouse events differ. Every example runs in the preview under it.

Giving an element a handler

A handler is a function the browser calls when something happens to an element. In Salam you attach it with an attribute named after the event: click = ... on a button says what to do when the button is clicked. Events with two words have a space, as everywhere in the page language: double click, mouse move, context menu. The value of the attribute can be written in four ways.

web-mouse-forms.salam
import dom

mut named_clicks := 0

func named():
    named_clicks += 1
    dom.SetText(dom.ById("out"), "named function, click " + named_clicks)
end

func with_event(e: i64):
    dom.SetText(dom.ById("out"), "named function with the event, pointer at x " + dom.EventX(e))
end

page:
    title = "Four ways to handle a click"
    global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
    global: selector = "button" padding = "8px 14px" margin = "0 8px 8px 0" font size = "1rem" end
    paragraph: id = "out" content = "Press a button." end
    button: click = named content = "Named function" end
    button: click = with_event content = "With the event" end
    button: click = (): dom.SetText(dom.ById("out"), "lambda") end content = "Lambda" end
    button: click = "this.textContent = 'JS string was here'" content = "JS string" end
end

Here is what each form is for.

  • A named function declared above the page: block. Use it when the code is longer than a line or when two elements share it. The function may have no parameters, as named has, or one parameter of type i64, as with_event has. That parameter receives the event, and you hand it to the helpers that read it: dom.EventX(e) and dom.EventY(e) for the pointer position, dom.EventTarget(e) for the element that was hit, dom.PreventDefault(e) and dom.StopPropagation(e) to change what the browser does next.
  • A lambda written inline as (): ... end. It is the shortest form for one or two statements. In a page attribute a lambda takes no parameters, so it cannot see the event. When you need the event, use a named function.
  • A JavaScript string such as "this.textContent = 'hi'". It is copied into the HTML as it is, so inside it this is the element and event is the event. Use it only for tiny things the Salam helpers do not cover.

The functions you write in Salam are compiled to JavaScript and placed in one script at the end of the page. A browser finds the function named in onclick="named()" by looking it up on window, so the compiler also stores every handler there. You never write this yourself, but it explains why a handler must be a plain function of the page and not, say, a method.

State, for example a counter, lives in a top level mut variable above the page. The handler changes it and then writes the new value into the page with dom.SetText. The compiler checks your work: dom.ById("x") must name an id that exists in the same page (error E138), and an event must name a function that exists (error E137).

Do not name a handler like a built-in. The browser looks a name in an attribute up on the element, then on its form, then on the document, and only then on the window. A function called clear, open, close, write, reset, submit, focus, click or name loses to the built-in of that name, and nothing seems to happen. Some names are worse than useless: the window has read-only properties such as closed, status, top and length, and a handler with one of those names makes the whole page script stop with an error, so none of your handlers work. Choose names such as clear_board or open_menu.

A page script only knows the dom helpers and your own code. Helpers that do more than pass one call on, such as dom.RandomInt, and other packages such as math are not part of it. dom.Random() gives a number from 0 up to 1, so (dom.Random() * 85.0) as i64 is a whole number from 0 to 84.

Click: counting and switching

click fires when the user presses and releases the primary mouse button on the element, and also when a keyboard user presses Enter or Space on a focused button, so it is the right event for anything that acts like a button. Use it on a real button element and not on a box: a button can be reached with the Tab key, a box cannot.

The counter below keeps its number in count. Each handler is a lambda that changes the variable and calls show, a small function that copies the number into the heading. The two parts, the state and the code that draws it, stay separate, and that is the habit worth learning first.

web-mouse-counter.salam
import dom

mut count := 0

func show:
    dom.SetText(dom.ById("n"), "" + count)
end

page:
    title = "Click counter"
    global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" text align = center end
    heading: id = "n" size = 1 content = "0" font size = "4rem" margin = "0 0 12px 0" end
    button: id = "plus" click = (): count += 1 show() end content = "+1" padding = "8px 18px" font size = "1.1rem" end
    button: id = "reset" click = (): count = 0 show() end content = "Reset" padding = "8px 18px" font size = "1.1rem" margin = "0 0 0 8px" end
end

The second example needs no variable, because the page itself remembers the state: dom.ToggleClass adds a class when the element does not have it and removes it when it does. The look of each state is written in CSS, in a style element, and the script only flips the class. The transition property makes the change fade instead of jump.

web-mouse-toggle.salam
import dom

page:
    title = "Lamp"
    style: content = "#room.on { background: #fde68a; color: #422006 } #room.on .bulb { background: #f59e0b }" end
    global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
    box:
        id = "room"
        background = "#1e293b"
        color = "#e2e8f0"
        padding = 24
        border radius = 12
        text align = center
        transition = "background 0.3s"
        box: class = "bulb" width = 60 height = 60 margin = "0 auto 12px" background = "#475569" border radius = "50%" end
        button: id = "switch" click = (): dom.ToggleClass(dom.ById("room"), "on") end content = "Switch" padding = "6px 16px" end
    end
end

Double click, other buttons and the context menu

double click fires after two quick clicks on the same element. The browser still sends the two ordinary click events first, so a handler on click runs twice before the double click arrives. Do not put two different actions on click and double click of the same element. aux click is the click of every button except the main one. The event's button number tells which: 0 is the main button, 1 the middle one, 2 the right one, and 3 and 4 are the back and forward buttons of some mice. context menu fires when the user asks for the context menu, which is a right click, the menu key or Shift and F10. Calling dom.PreventDefault(e) stops the browser's own menu.

web-mouse-buttons.salam
import dom

mut history := ""
mut lines := 0

func note(text: str):
    lines += 1
    if lines > 7:
        history = ""
        lines = 1
    end
    history = history + text + "\n"
    dom.SetText(dom.ById("log"), history)
end

func aux(e: i64):
    note("aux click, button " + (dom.GetNum(e, "button") as i64))
end

func menu(e: i64):
    dom.PreventDefault(e)
    note("context menu (the browser menu is blocked)")
end

page:
    title = "Kinds of clicks"
    global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
    box:
        id = "pad"
        click = (): note("click") end
        double click = (): note("double click") end
        aux click = aux
        context menu = menu
        content = "Click, double click, middle click or right click here"
        padding = 28
        background = "#e0f2fe"
        border radius = 10
        user select = none
    end
    paragraph: id = "log" content = "Nothing yet." white space = "pre-line" min height = "10em" end
end

The example reads the button number with dom.GetNum(e, "button"), which reads any number property of the event object. The log holds the last seven events, and white space = "pre-line" turns each \n in the text into a new line. user select = none stops a double click from selecting the words of the box.

That is enough to build your own menu. On context menu the handler prevents the default, moves a hidden box to the pointer and shows it. Any click on the area closes it again, and each item closes it as well.

web-mouse-menu.salam
import dom

func open_menu(e: i64):
    dom.PreventDefault(e)
    menu := dom.ById("menu")
    dom.SetStyle(menu, "left", "" + dom.EventX(e) + "px")
    dom.SetStyle(menu, "top", "" + dom.EventY(e) + "px")
    dom.SetStyle(menu, "display", "block")
end

func close_menu():
    dom.SetStyle(dom.ById("menu"), "display", "none")
end

func pick(what: str):
    dom.SetText(dom.ById("chosen"), "You chose: " + what)
    close_menu()
end

page:
    title = "Custom menu"
    global: selector = "body" font family = "system-ui, sans-serif" margin = 0 end
    box:
        id = "area"
        context menu = open_menu
        click = close_menu
        padding = 32
        min height = "260px"
        background = "#f1f5f9"
        paragraph: content = "Right click anywhere on this area." end
        paragraph: id = "chosen" content = "Nothing chosen." end
    end
    box:
        id = "menu"
        display = none
        position = fixed
        background = white
        border = "1px solid #cbd5e1"
        border radius = 8
        box shadow = "0 6px 18px rgba(0,0,0,0.18)"
        padding = 4
        button: id = "copy" click = (): pick("Copy") end content = "Copy" display = block width = "100%" end
        button: id = "rename" click = (): pick("Rename") end content = "Rename" display = block width = "100%" end
        button: id = "delete" click = (): pick("Delete") end content = "Delete" display = block width = "100%" end
    end
end

The menu box has position = fixed, which places it relative to the window, so left and top can be set straight from the pointer position dom.EventX(e) and dom.EventY(e). Those two are measured from the top left corner of the window, which is the same corner a fixed box uses.

Use a custom context menu only where it adds something, as in an editor or a file list. A right click that does nothing useful, or hides the ordinary menu with its spell check and copy, annoys people. Keep every action of the menu reachable in another way too, because touch screens and keyboards do not right click.

Pressing, releasing and moving

mouse down fires the moment a button goes down, mouse up when it comes up, and click is what the browser makes of a down and an up on the same element. mouse move fires again and again while the pointer moves, many times a second, so keep its handler short.

The position in the event is measured from the window. Most of the time you want it measured from some box, such as a drawing area. Ask the box where it is with getBoundingClientRect and subtract its left and top. In Salam that is dom.Call0(box, "getBoundingClientRect") for the rectangle and dom.GetNum(rect, "left") for each number. The first example shows the position inside the green box and moves a dot with it. The dot has pointer events = none, so it never gets in the way of the pointer.

web-mouse-coords.salam
import dom

func moved(e: i64):
    pad := dom.ById("pad")
    r := dom.Call0(pad, "getBoundingClientRect")
    x := (dom.EventX(e) - dom.GetNum(r, "left")) as i64
    y := (dom.EventY(e) - dom.GetNum(r, "top")) as i64
    dom.SetText(dom.ById("pos"), "x " + x + ", y " + y)
    dot := dom.ById("dot")
    dom.SetStyle(dot, "left", "" + x + "px")
    dom.SetStyle(dot, "top", "" + y + "px")
end

page:
    title = "Pointer position"
    global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
    box:
        id = "pad"
        mouse move = moved
        position = relative
        height = 180
        background = "#ecfccb"
        border radius = 10
        cursor = crosshair
        overflow = hidden
        box: id = "dot" position = absolute width = 12 height = 12 margin = "-6px 0 0 -6px" border radius = "50%" background = "#15803d" pointer events = none end
    end
    paragraph: id = "pos" content = "Move the mouse over the green area." end
end

The next example draws. A top level mut variable says whether the button is down. mouse down sets it and paints the first dot, mouse move paints while it is set, and mouse up clears it. mouse leave clears it too, because a button released outside the box would never reach the box. Each dot is a new element made with dom.Create and added with dom.Append, and the look of the dots comes from one CSS rule written in a style element.

web-mouse-draw.salam
import dom

mut drawing := false

func begin_stroke(e: i64):
    drawing = true
    paint(e)
end

func end_stroke:
    drawing = false
end

func paint(e: i64):
    if drawing:
        r := dom.Call0(dom.ById("board"), "getBoundingClientRect")
        dot := dom.Create("div")
        dom.SetAttr(dot, "class", "dot")
        dom.SetStyle(dot, "left", "" + (dom.EventX(e) - dom.GetNum(r, "left")) + "px")
        dom.SetStyle(dot, "top", "" + (dom.EventY(e) - dom.GetNum(r, "top")) + "px")
        dom.Append(dom.ById("board"), dot)
    end
end

func clear_board:
    dom.SetHtml(dom.ById("board"), "")
end

page:
    title = "Drawing"
    style: content = ".dot { position: absolute; width: 10px; height: 10px; margin: -5px 0 0 -5px; border-radius: 50%; background: #7c3aed; pointer-events: none }" end
    global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
    box:
        id = "board"
        mouse down = begin_stroke
        mouse move = paint
        mouse up = end_stroke
        mouse leave = end_stroke
        position = relative
        height = 200
        background = "#faf5ff"
        border = "2px dashed #c4b5fd"
        border radius = 10
        cursor = crosshair
        overflow = hidden
        user select = none
    end
    button: id = "clear" click = clear_board content = "Clear" margin = "10px 0 0 0" padding = "6px 14px" end
end

This draws dots, so a fast stroke leaves gaps. For a smooth line use a canvas, and see web/canvas. The pattern of down, move and up is the same for dragging an element by hand, and the next lessons build on it.

Enter and leave, over and out

Four events tell you that the pointer has come or gone, and two pairs behave differently. mouse enter and mouse leave fire only for the element they are set on: when the pointer first comes into the element, and when it goes out of it altogether. Moving between the element and its children does not count. mouse over and mouse out also fire for the children, because they bubble up from them: moving from the outer box onto the inner one sends an out for the outer box and an over that rises from the inner one. Try both below and watch the counters. The left box says enter 1, leave 0 while you are over it or its inner square, and the right box counts every step.

web-mouse-enter.salam
import dom

mut enters := 0
mut leaves := 0
mut overs := 0
mut outs := 0

func show_a:
    dom.SetText(dom.ById("count-a"), "enter " + enters + ", leave " + leaves)
end

func show_b:
    dom.SetText(dom.ById("count-b"), "over " + overs + ", out " + outs)
end

page:
    title = "Enter and leave"
    global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
    global: selector = ".zone" position = relative height = 90 width = 220 background = "#dbeafe" border radius = 10 padding = 10 box sizing = "border-box" end
    global: selector = ".inner" position = absolute right = 10 top = 10 width = 70 height = 70 background = "#2563eb" color = white border radius = 8 text align = center end
    box:
        display = flex
        gap = 24
        flex wrap = wrap
        box:
            heading: size = 3 content = "enter and leave" margin = "0 0 8px 0" end
            box:
                id = "a" class = "zone" content = "outer"
                mouse enter = (): enters += 1 show_a() end
                mouse leave = (): leaves += 1 show_a() end
                box: id = "a-inner" class = "inner" content = "inner" end
            end
            paragraph: id = "count-a" content = "enter 0, leave 0" end
        end
        box:
            heading: size = 3 content = "over and out" margin = "0 0 8px 0" end
            box:
                id = "b" class = "zone" content = "outer"
                mouse over = (): overs += 1 show_b() end
                mouse out = (): outs += 1 show_b() end
                box: id = "b-inner" class = "inner" content = "inner" end
            end
            paragraph: id = "count-b" content = "over 0, out 0" end
        end
    end
end

Most of the time you want enter and leave: a menu that opens while the pointer is over it, a tooltip, a highlight. Use over and out when you really want to know about each child, for example to light up the exact cell of a table the pointer touches.

Here is a tooltip that follows the pointer. mouse enter shows it with the right text, mouse move moves it, and mouse leave hides it. It sits 14 and 18 pixels away from the pointer, because a tooltip under the pointer would take the pointer's events and flicker. Its pointer events = none prevents the same problem.

web-mouse-tooltip.salam
import dom

func show_tip(text: str):
    tip := dom.ById("tip")
    dom.SetText(tip, text)
    dom.SetStyle(tip, "display", "block")
end

func hide_tip:
    dom.SetStyle(dom.ById("tip"), "display", "none")
end

func follow(e: i64):
    tip := dom.ById("tip")
    dom.SetStyle(tip, "left", "" + (dom.EventX(e) + 14.0) + "px")
    dom.SetStyle(tip, "top", "" + (dom.EventY(e) + 18.0) + "px")
end

page:
    title = "Tooltip"
    global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
    global: selector = "button" padding = "8px 14px" margin = "0 8px 0 0" font size = "1rem" end
    button: id = "save" content = "Save" mouse enter = (): show_tip("Save the file") end mouse move = follow mouse leave = hide_tip end
    button: id = "open" content = "Open" mouse enter = (): show_tip("Open a file from disk") end mouse move = follow mouse leave = hide_tip end
    button: id = "share" content = "Share" mouse enter = (): show_tip("Send a link to a friend") end mouse move = follow mouse leave = hide_tip end
    box: id = "tip" display = none position = fixed background = "#0f172a" color = white padding = "4px 10px" border radius = 6 font size = "0.85rem" pointer events = none end
end

The target and bubbling

When you click the word inside a button inside a box, the event first goes to the word, and then it bubbles: it is offered to the button, then to the box, then upwards to the page. Every element on the way that has a click handler runs it. Inside a handler, dom.EventTarget(e) is the element that was really hit, even when the handler is set on an ancestor. This lets one handler on a list serve a hundred items. Call dom.StopPropagation(e) and the event goes no further.

web-mouse-bubble.salam
import dom

func outer(e: i64):
    who := dom.GetAttr(dom.EventTarget(e), "id")
    dom.SetText(dom.ById("log"), "The outer box heard a click from: " + who)
end

func inner_stop(e: i64):
    dom.StopPropagation(e)
    dom.SetText(dom.ById("log"), "The stopping button kept the click to itself.")
end

page:
    title = "Bubbling"
    global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
    global: selector = "button" padding = "8px 14px" margin = "0 8px 0 0" font size = "1rem" end
    box:
        id = "outer-box"
        click = outer
        padding = 20
        background = "#ede9fe"
        border radius = 10
        paragraph: content = "Outer box" margin = "0 0 10px 0" end
        button: id = "plain" content = "Plain button" end
        button: id = "stopper" click = inner_stop content = "Stopping button" end
        span: id = "label" content = "or the text here" end
    end
    paragraph: id = "log" content = "Click something." end
end

The outer box has one handler. It reads the target's id with dom.GetAttr and prints it. The plain button has no handler of its own and still reports to the box, and so does the text. The stopping button has a handler that stops the event, so the box never hears from it. Do not stop propagation without a reason: other code that listens higher up, such as a menu that wants to close on any click, stops working for that element.

Most events bubble. A few do not, and mouse enter and mouse leave are the ones from this lesson. This is why they count differently from over and out above.

The wheel

wheel fires when the wheel turns, or two fingers slide on a touchpad. The event's deltaY is negative when the user scrolls up, away from themselves, and positive when they scroll down. By default the wheel scrolls the page. A handler that calls dom.PreventDefault(e) cancels that, which is what you want in a map or an image viewer and what you should not do on a normal page. Most browsers treat wheel handlers on the window, document and body as passive and ignore the cancel there, so put the handler on an element, as the example does.

web-mouse-wheel.salam
import dom

mut zoom := 1.0

func scroll_zoom(e: i64):
    dom.PreventDefault(e)
    if dom.GetNum(e, "deltaY") < 0.0:
        zoom = zoom * 1.1
    else:
        zoom = zoom / 1.1
    end
    if zoom > 3.0:
        zoom = 3.0
    end
    if zoom < 0.5:
        zoom = 0.5
    end
    percent := (zoom * 100.0) as i64
    dom.SetStyle(dom.ById("card"), "transform", "scale(" + zoom + ")")
    dom.SetText(dom.ById("level"), "Zoom " + percent + "%")
end

page:
    title = "Wheel zoom"
    global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
    box:
        id = "stage"
        wheel = scroll_zoom
        height = 200
        overflow = hidden
        background = "#f1f5f9"
        border radius = 10
        display = flex
        align items = center
        justify content = center
        box: id = "card" content = "Scroll the wheel over me" padding = 24 background = "#0f766e" color = white border radius = 10 end
    end
    paragraph: id = "level" content = "Zoom 100%" end
end

The handler multiplies or divides the zoom by 1.1 on each step and keeps it between 0.5 and 3, with two plain if statements, because the page script has no math. The result is written into transform as scale(...). The box that is scaled is inside a stage with overflow = hidden, so what grows beyond the stage is cut off and the page does not move.

Putting it together: a click game

This game uses what you have seen. The red square has a handler for click that counts a hit and moves the square to a random place. The yellow field around it also has a click handler that counts a miss. A click on the square would bubble to the field and count as a miss as well, so the square's handler first calls dom.StopPropagation(e). Positions are set in percent, so the game works at any width.

web-mouse-game.salam
import dom

mut hits := 0
mut misses := 0

func show:
    dom.SetText(dom.ById("score"), "Hits " + hits + ", misses " + misses)
end

func move_target:
    t := dom.ById("target")
    dom.SetStyle(t, "left", "" + ((dom.Random() * 85.0) as i64) + "%")
    dom.SetStyle(t, "top", "" + ((dom.Random() * 75.0) as i64) + "%")
end

func hit(e: i64):
    dom.StopPropagation(e)
    hits += 1
    show()
    move_target()
end

func miss:
    misses += 1
    show()
end

page:
    title = "Catch the square"
    global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
    box:
        id = "field"
        click = miss
        position = relative
        height = 220
        background = "#fef9c3"
        border radius = 10
        overflow = hidden
        cursor = crosshair
        box: id = "target" click = hit position = absolute left = "40%" top = "30%" width = 44 height = 44 background = "#dc2626" border radius = 8 cursor = pointer end
    end
    paragraph: id = "score" content = "Hits 0, misses 0" end
end

When CSS is the better tool

Before you write mouse over and mouse out to change how something looks, remember that CSS can do it alone. A style with the prefix hover applies while the pointer is over the element, and the browser takes it away again, with no code that can forget a case. The two buttons below look the same. The first uses hover background and states each colour once. The second needs two handlers, repeats the colours in the script, and does nothing when the script fails to load. CSS also takes the hover style away by itself in every case, while a script has to remember each way the pointer can leave.

web-mouse-hover.salam
import dom

page:
    title = "Hover"
    global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
    global: selector = "button" padding = "10px 18px" margin = "0 12px 0 0" font size = "1rem" border = "none" border radius = 8 color = white end
    button:
        id = "css"
        content = "Hover in CSS"
        background = "#0f766e"
        transition = "background 0.2s"
        hover background = "#115e59"
    end
    button:
        id = "js"
        content = "Hover in JS"
        background = "#0f766e"
        mouse over = (): dom.SetStyle(dom.ById("js"), "background", "#115e59") end
        mouse out = (): dom.SetStyle(dom.ById("js"), "background", "#0f766e") end
    end
    paragraph: content = "Both look the same. Only the first one needs no script." end
end

Use a mouse event when something must happen, such as a number that changes, a box that appears, a request that is sent. Use CSS when the page only has to look different. The CSS lessons come back to hover, focus and active in detail.

Touch screens have no pointer to hover with. A tooltip or a menu that appears only on hover is out of reach for a phone, so also offer a click or a tap.

Reference: every mouse event

Each row gives the attribute, the name of the HTML attribute it becomes and when the event fires. The snippets are checked by the compiler. Where a handler needs the event itself, the snippet uses a JavaScript string, because a pasted snippet has no named function to call; in your own code use a named function with an i64 parameter, as in the examples above.

AttributeWhen it firesExample
click
کلیک
onclick
Runs when the primary button is pressed and released on the element, or when a keyboard user activates a focused button with Enter or Space. It is the event for anything that acts like a button.button: click = (): dom.SetText(dom.ById("out"), "Clicked") end content = "Click me" end paragraph: id = "out" content = "-" end
double click
دو کلیک
ondblclick
Runs after two quick clicks on the same element. The two ordinary click events come first, so a click handler on the same element runs twice before this one.box: double click = (): dom.SetText(dom.ById("out"), "Double clicked") end content = "Double click here" padding = 20 background = "#e0f2fe" user select = none end paragraph: id = "out" content = "-" end
aux click
کلیک کمکی
onauxclick
Runs when a button other than the main one is clicked. The event property button says which: 1 is the middle button and 2 the right one, and 3 and 4 are the back and forward buttons of some mice.box: aux click = (): dom.SetText(dom.ById("out"), "Another button") end content = "Middle click here" padding = 20 background = "#fef3c7" end paragraph: id = "out" content = "-" end
context menu
منوی زمینه
oncontextmenu
Runs when the user asks for the context menu: a right click, the menu key or Shift and F10. dom.PreventDefault(e) stops the browser's own menu so the page can show its own.box: context menu = "event.preventDefault(); this.textContent = 'Your own menu goes here'" content = "Right click here" padding = 20 background = "#fce7f3" end
mouse down
فشردن ماوس
onmousedown
Runs the moment a mouse button goes down on the element, before the button is released. It is where dragging and drawing start.box: mouse down = (): dom.SetText(dom.ById("out"), "Button is down") end mouse up = (): dom.SetText(dom.ById("out"), "Button is up") end content = "Press and hold" padding = 20 background = "#e0e7ff" user select = none end paragraph: id = "out" content = "-" end
mouse up
رها کردن ماوس
onmouseup
Runs when a mouse button comes up over the element. A button released outside the element where it went down is reported to the element under the pointer, not to the first one.box: mouse up = (): dom.SetText(dom.ById("out"), "Released here") end content = "Release the button over me" padding = 20 background = "#dcfce7" user select = none end paragraph: id = "out" content = "-" end
mouse move
حرکت ماوس
onmousemove
Runs again and again while the pointer moves over the element, many times a second. Keep the handler short. The position is dom.EventX(e) and dom.EventY(e), measured from the window.box: mouse move = "this.textContent = event.clientX + ', ' + event.clientY" content = "Move over me" padding = 24 background = "#dcfce7" end
mouse enter
ورود ماوس
onmouseenter
Runs once when the pointer comes into the element. It does not bubble and does not repeat when the pointer moves between the element and its children, which makes it the right choice for hover effects done in script.box: mouse enter = (): dom.SetText(dom.ById("out"), "Entered") end content = "Point at me" padding = 20 background = "#e0f2fe" end paragraph: id = "out" content = "-" end
mouse leave
خروج ماوس
onmouseleave
Runs once when the pointer leaves the element and all its children. It does not bubble.box: mouse leave = (): dom.SetText(dom.ById("out"), "Left") end content = "Point at me, then move away" padding = 20 background = "#e0f2fe" end paragraph: id = "out" content = "-" end
mouse over
عبور ماوس
onmouseover
Runs when the pointer comes onto the element or onto one of its children. It bubbles, so a handler on a parent hears about every child; use dom.EventTarget(e) to see which one.box: mouse over = (): dom.SetText(dom.ById("out"), "Over") end content = "Point at me" padding = 20 background = "#fef3c7" end paragraph: id = "out" content = "-" end
mouse out
بیرون رفتن ماوس
onmouseout
Runs when the pointer goes off the element or off one of its children. It bubbles, and it also fires when the pointer moves from the element onto its own child.box: mouse out = (): dom.SetText(dom.ById("out"), "Out") end content = "Point at me, then move away" padding = 20 background = "#fef3c7" end paragraph: id = "out" content = "-" end
wheel
چرخ ماوس
onwheel
Runs when the mouse wheel turns or two fingers slide on a touchpad. The event property deltaY is negative when scrolling up and positive when scrolling down. dom.PreventDefault(e) stops the page from scrolling.box: wheel = "event.preventDefault(); this.textContent = event.deltaY < 0 ? 'Up' : 'Down'" content = "Turn the wheel here" padding = 24 background = "#ede9fe" end

Try it yourself

  • Change the counter so that a double click adds ten and a click adds one. Watch the counter on a double click: what does it show? Why?
  • Make the drawing board paint in a colour that depends on the button: left button purple, middle button green. Use mouse down and the button number.
  • Add a "score" for the click game that counts how many hits in a row you made without a miss, and show it in the paragraph.
  • Give the lamp example a second button and make both buttons control the same lamp with one named function.