-
Star
(227)
You must be signed in to star a gist -
Fork
(29)
You must be signed in to fork a gist
-
-
Save wybiral/c8f46fdf1fc558d631b55de3a0267771 to your computer and use it in GitHub Desktop.
| // 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() | |
| } | |
| } |
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.
@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.
Yea, I figured it out by rewatching the demo and noticing the page never finished loading ^^
Thanks for the quick reply, though! :>
Where does the data go or how would i be able to capture it?
it pretty simple actually there two windows open as we can see in the demo, one move the cursor other one draws
- connections are kept open and response is sent in chunks
- one handler send blocks with style hover background image
- on hover image is requested from the server ( secret sauce making it tick)
- handler for image with coordinates (x,y) puts the data on the go channel
- watch handler go throughs the channel and push it to the other window
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 addpointer-events: none;to it,pointer-events: auto;to thep, andpointer-events: none;top: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:
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.