-
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.