Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Tracking cursor position in real-time with remote monitoring (without JavaScript)
// Tracking cursor position in real-time without JavaScript
// Demo: https://twitter.com/davywtf/status/1124146339259002881
package main
import (
"fmt"
"net/http"
"strings"
)
const W = 50
const H = 50
var ch chan string
const head = `<head>
<style>
*{margin:0;padding:0}
html,body{width:100%;height:100%}
p{
width:10px;
height:10px;
display:inline-block;
border-right:1px solid #666;
border-bottom:1px solid #666
}
</style>
</head>`
func main() {
ch = make(chan string)
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
func handler(w http.ResponseWriter, r *http.Request) {
p := r.URL.Path
if p == "/" {
index(w, r)
return
} else if p == "/watch" {
watch(w, r)
return
} else {
if strings.HasPrefix(p, "/c") && strings.HasSuffix(p, ".png") {
ch <- p[1 : len(p)-4]
}
}
}
func index(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
flusher, ok := w.(http.Flusher)
if !ok {
return
}
w.Write([]byte(head))
flusher.Flush()
// Send <p> grid
w.Write([]byte("<body>\n"))
for i := 0; i < H; i++ {
w.Write([]byte("<div>"))
for j := 0; j < W; j++ {
w.Write([]byte(fmt.Sprintf("<p id=\"c%dx%d\"></p>", i, j)))
}
w.Write([]byte("</div>\n"))
}
w.Write([]byte("</body>\n"))
flusher.Flush()
// Send CSS
w.Write([]byte("<style>"))
for i := 0; i < H; i++ {
for j := 0; j < W; j++ {
id := fmt.Sprintf("c%dx%d", i, j)
s := fmt.Sprintf("#%s:hover{background:url(\"%s.png\")}", id, id)
w.Write([]byte(s))
}
}
w.Write([]byte("</style>"))
flusher.Flush()
}
func watch(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
flusher, ok := w.(http.Flusher)
if !ok {
return
}
w.Write([]byte(head))
flusher.Flush()
// Send <p> grid
w.Write([]byte("<body>\n"))
for i := 0; i < H; i++ {
w.Write([]byte("<div>"))
for j := 0; j < W; j++ {
w.Write([]byte(fmt.Sprintf("<p id=\"c%dx%d\"></p>", i, j)))
}
w.Write([]byte("</div>\n"))
}
w.Write([]byte("</body>\n"))
flusher.Flush()
// Listen to ch for updates and write to response
for p := range ch {
s := fmt.Sprintf("<style>#%s{background:#000}</style>\n", p)
_, err := w.Write([]byte(s))
if err != nil {
return
}
flusher.Flush()
}
}
@straube

This comment has been minimized.

Copy link

@straube straube commented May 6, 2019

I was playing with this snippet and some extra CSS. It seems possible to add a grid covering an entire page.

To do that, it's required to wrap the tracking points in another element (a <div>, perhaps) and make it fixed positioned at 0 x 0. Then add pointer-events: none; to it, pointer-events: auto; to the p, and pointer-events: none; to p:hover.

Even it's not possible (I guess) to get the viewport size on the server side, one could stretch the grid to cover a page using CSS flexbox or grid layout. It's not a perfect solution but it could work.

Here is a summary of the styling I mentioned above:

.track-wrapper {
    position: fixed;
    top: 0; left: 0;
    pointer-events: none;
}

.p {
    pointer-events: auto;
}

.p:hover {
    pointer-events: none;
}

One known issue of this approach is some glitches when hovering a link, for example. The cursor will quickly toggle between pointer and arrow while the mouse is moving.

@fr3fou

This comment has been minimized.

Copy link

@fr3fou fr3fou commented Jul 1, 2019

How does the live-preview work? I can see that it's writing to the writer but when does it send the request to the client? Shouldn't the client also make a new request to the server to get the new stylesheet? I'm new to go and it's sorta confusing to me.

@wybiral

This comment has been minimized.

Copy link
Owner Author

@wybiral wybiral commented Jul 2, 2019

@fr3fou it uses chunk transfer encoding. So the server connection stays open and wait for more parts of the web page (almost as though it's a really slow loading web page). The server sends pieces of CSS in real-time (as though it's slowly loading, but really it's real-time).

But, yeah, the chunked transfer encoding is key. The http server is allowed to send blocks of HTML over a TCP connection instead of the typical request/response setup.

@fr3fou

This comment has been minimized.

Copy link

@fr3fou fr3fou commented Jul 2, 2019

Yea, I figured it out by rewatching the demo and noticing the page never finished loading ^^

Thanks for the quick reply, though! :>

@AHP873

This comment has been minimized.

Copy link

@AHP873 AHP873 commented Jul 25, 2019

Where does the data go or how would i be able to capture it?

@allyraza

This comment has been minimized.

Copy link

@allyraza allyraza commented Apr 18, 2020

it pretty simple actually there two windows open as we can see in the demo, one move the cursor other one draws

  1. connections are kept open and response is sent in chunks
  2. one handler send blocks with style hover background image
  3. on hover image is requested from the server ( secret sauce making it tick)
  4. handler for image with coordinates (x,y) puts the data on the go channel
  5. watch handler go throughs the channel and push it to the other window
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.