Fed up with MouseEnter and MouseLeave not working? Here's a module for you!

For a long time now, I’ve found MouseEnter and MouseLeave events to be rather unreliable. I’ve created a simple module to replace these for my new game, and thought I’d share it with you all in the hopes it would save some frustration and annoyance.

Does not support touch or game pads. Please report any bugs you find!

Note: Currently, once connected the events will continue to be fired until the GuiObject is destroyed, regardless of the UIs parent or visibility.

Note 2: Does not work for rotated elements.

Quick Start Guide:

  1. Copy the code at the bottom into a module and call it ‘MouseOverModule’, or a name of your choosing. Place it wherever you normally keep your modules.
  2. Use like this:
local MouseOverModule = require(game.ReplicatedStorage.Whatever.MouseOverModule)

local MouseEnter, MouseLeave = MouseOverModule.MouseEnterLeaveEvent(GuiObject)
MouseEnter:Connect(function()
   print("Mouse enter")
end

MouseLeave:Connect(function()
   print("Mouse leave")
end
  1. Delete the GuiObject when you’re done with it.
local Player = game.Players.LocalPlayer or game.Players:GetPropertyChangedSignal("LocalPlayer")

local Mouse = Player:GetMouse()

local CurrentItems = {}

--Private functions
local function IsInFrame(v)

    local X = Mouse.X
    local Y = Mouse.Y

    if X>v.AbsolutePosition.X and Y>v.AbsolutePosition.Y and X<v.AbsolutePosition.X+v.AbsoluteSize.X and Y<v.AbsolutePosition.Y+v.AbsoluteSize.Y then
        return true
    else 
        return false
    end
end

local function CheckMouseExited(Object)

    if not Object.MouseIsInFrame and Object.MouseWasIn then --Mouse was previously over object, fire leave event
        Object.MouseWasIn = false
        Object.LeaveEvent:Fire()
    end
end


local function CheckMouseEntered(Object)
    if Object.MouseIsInFrame and not Object.MouseWasIn then
        Object.MouseWasIn = true
        Object.EnteredEvent:Fire()
    end
end

game:GetService("RunService").Heartbeat:Connect(function()
    --Check each UI object
    --All exit events fire before all enter events for ease of use, so check for mouse exit events here
    for _, Object in pairs(CurrentItems) do
        Object.MouseIsInFrame = IsInFrame(Object.UIObj)
        CheckMouseExited(Object)
    end

    --Now check if the mouse entered any frames
    for _, Object in pairs(CurrentItems) do
        CheckMouseEntered(Object)
    end
end)

--Public functions

local module = {}

    function module.MouseEnterLeaveEvent(UIObj)
        if CurrentItems[UIObj] then
            return CurrentItems[UIObj].EnteredEvent.Event,CurrentItems[UIObj].LeaveEvent.Event
        end     

        local newObj = {}

        newObj.UIObj = UIObj

        local EnterEvent = Instance.new("BindableEvent")
        local LeaveEvent = Instance.new("BindableEvent")
        
        newObj.EnteredEvent = EnterEvent
        newObj.LeaveEvent = LeaveEvent
        newObj.MouseWasIn = false
        CurrentItems[UIObj] = newObj

        UIObj.AncestryChanged:Connect(function()
            if not UIObj.Parent then
                --Assuming the object has been destroyed as we still dont have a .Destroyed event
                --If for some reason you parent your UI elements to nil after calling this, then parent it back again, mouse over will still have been disconnected.
                EnterEvent:Destroy()    
                LeaveEvent:Destroy()    
                CurrentItems[UIObj] = nil
            end
        end)

       return EnterEvent.Event,LeaveEvent.Event
    end

return module
218 Likes
InputEnded does not fire on GUI objects when the object moves in the context of UserInputType.MouseMovement
Quick help on mouseleaving not always working when you do it too fast
ImageLabels - MouseEnter causing conflict with TouchSwipe
Problem with UI increasing in size constantly
TweenSize Issue
What are better alternatives for MouseEnter and MouseLeave?
Any way Similar to MouseEnter and MouseLeave that works better?
Problem with MouseLeave
Hovering Module | Better alternative to MouseEnter and MouseLeave!
MouseLeave Function Not Working!
Is this the best way to make a gui hover animation?
HoverDetect Script? How?
MouseLeave sometimes doesn't fire, is there a workaround?
UIStroke Bug? Changing the thickness on .MouseEnter
Unstable Tween, need help
How can I get MouseEnter and MouseLeave to work properly?
MouseEnter & Leave are buggy
Issue with mouseenter
How do I script a customizable inventory?
How to get accurate MouseEnter/MouseLeave
Getting the name of a gui using mouse
.MouseLeave event firing after .MouseEnter on adjacent buttons..?
Issues with mouse enter and mouse leave
How can I fix this logic issue with a custom mouse event implementation?
Issues with mouse enter and mouse leave
Tweening GUI ends up breaking them
How to make GUI MouseEnter/MouseLeave more responsive?
Any way I can bypass a weird behavior with MouseEnter/Leave events?
MouseLeave Function Not working!
MouseLeave Function Not working!
Why doesnt this work?
Mouse hovering over GUI button
Am I blind or is this a Roblox bug?

Thank you for making this. I needed this

3 Likes

GuiObject.InputBegan(inputObject) and GuiObject.InputEnded(inputObject) where inputObj.UserInputType == UserInputType.MouseMovement have also been reliable ways to work around this in my experience.

42 Likes

This doesn’t work for me BillboardGui TextButton MouseMovement

2 Likes

I found if I used Button.MouseEnter then used a step bind and some quik maf to determine if it was in the button still, that was a simpler and equally efficient way of dealing with it.

Still, good job open sourcing.

2 Likes

Maybe you could account for the mouse to be on only one object at an instant.

Great work, will definitely prove to be useful. I also heard from a friend that works at Roblox that MouseEnter and MouseLeave events are being improved to fix their issues. Can’t confirm if fixes will be released soon though.

1 Like

Note 4: Doesn’t work for SurfaceGui’s neither it does account for parameters as ZIndex, Active or DisplayOrder. I might work those stuff in order to make them more reliable and keep the functionality of the default events.

Nice job, however.

I’ll probably get to adding the none-surface gui features at some point if you don’t beat me to it :slightly_smiling_face: (SurfaceGui mouse over would probably need a separate function and a lot more work)

3 Likes

To clarify a bit more, if you’re referring to note 3, the mouse can move from one object to another in a single frame, and currently the code has no guarantee that the leave event will fire before the other objects enter event .

4 Likes

This is really needed, too lazi to implement it now tho :stuck_out_tongue:

However I have some suggestions:

  1. Why not just return the MouseEnterLeaveEvent function?
    return MouseEnterLeaveEvent

  2. The function should return the Event signal instead of the Event object, so can connect directly:
    return EnterEvent.Event.Event, LeaveEvent.Event.Event

This would allow to do:

local MouseEnter, MouseLeave = MouseOverModule(GuiObject)
MouseEnter:connect(function()
   print("Mouse enter")
end
MouseLeave:connect(function()
   print("Mouse leave")
end

Which is slightly cleaner.

Alternativly allow include mouseenter/leave functions as parameters instead.
MouseOverModule(GuiObject, enterFunc, leaveFunc)

2 Likes

you are my everything.

1 Like

This is so perfect

Updated with a new version:
-Fixed a bug causing the module to break if you call .MouseEnterLeaveEvent(GuiObject) on the same object more than once
-Within the same frame, mouse leave events now always fire before mouse enter events. Should help make the module overall easier to use.

3 Likes

This is a really great module and so perfect!
Btw I found a problem in your demonstration

It should be MouseEnter:connect(function() and MouseLeave:connect(function() instead.

1 Like

I did. I finally did get fed up. Thank you.

In case anybody else wants to use this with roblox-ts, here’s a d.ts file for ya:

declare namespace MouseOver
{
function MouseEnterLeaveEvent( guiObject: GuiObject ) : [ RBXScriptSignal, RBXScriptSignal ]
}

export = MouseOver

3 Likes

Mouse Leave And Enter weren’t working, two seconds I installed this, it fixed all my problems. :smile: Thanks for this!

Finally got around to doing some restructuring. Updated a few things to follow better code practice (such as using an event to wait for local player instead of polling), and removed the really nasty spawn() statement, and slightly increased efficiency by making sure IsInFrame() is only called once per UI element, instead of twice.

After some consideration I switched from Mouse.Moved to RunService.RenderStepped, since this covers a lot of edge cases that would have bloated the code otherwise, while not really impacting efficiency much since the player will be moving their mouse almost every frame anyway.

There shouldn’t be any difference in how you use the module, unless you were somehow relying on there being a wait() between calling module.MouseEnterLeaveEvent, and MouseEnter firing if the player does not move their mouse when a button is spawned under it.

4 Likes

I believe this module still works for elements in a scrolling frame that aren’t currently being rendered on screen. Is there any way to get around this?

I recommend tying this to Heartbeat instead of RenderStepped.

Renderstepped should only really be used for camera or character code.

3 Likes