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

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

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

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

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

commented Jul 25, 2019

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

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.