I have tried doing this before, but was unable to get it working as completely as I wanted. I found that capturing the "keypress" event (or better yet "keyup" event) and re-writing the field value on that was FAIRLY good. (This is what most "masking" libraries do, and others have posted sample code already.) What I'll recount here is the PROBLEMS I had with this approach:
(1) It was difficult to manage the problem of people using the cursor keys to move back and forth through the field. There are notes out there on how to capture cursor keys in a cross-platform fashion but it's slightly tricky.
(2) Handling pressing of the delete key was even more of a pain (especially since they can cursor or click to anywhere in the field). Just removing the character before the cursor when they hit delete was NOT a good user experience: you would get situations where people couldn't delete stuff! Imagine they type "123.4", then hit delete 6 times in a row. The first delete removes the "4" (good so far), the next one removes the ".". Now your formatting rules see "123" and they add in the decimal for "123." The user can hit delete as often as they like and it won't go anywhere! We solved this by having delete remove the previous important character (digits in your case) instead of the previous character.
(3) Handling paste was one we never solved. Pasting via "ctrl-v" generate a key event that can be caught on different platforms, but pasting via the edit menu just couldn't be caught consistently across platforms. Eventually we just gave up on it.
I hope some of this experience helps. I'd love to share my code, but unfortunately it was for work and I'm not permitted to.