-
Notifications
You must be signed in to change notification settings - Fork 1
/
scroll.go
133 lines (109 loc) · 3.73 KB
/
scroll.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
package main
import (
"fmt"
"net/url"
"strings"
"time"
"github.com/gopherjs/gopherjs/js"
"github.com/shurcooL/go/gopherjs_http/jsutil"
"honnef.co/go/js/dom"
)
func setupScroll() {
js.Global.Set("AnchorScroll", jsutil.Wrap(AnchorScroll))
// Start watching for hashchange events.
dom.GetWindow().AddEventListener("hashchange", false, func(event dom.Event) {
processHash()
event.PreventDefault()
})
document.Body().AddEventListener("keydown", false, func(event dom.Event) {
if event.DefaultPrevented() {
return
}
switch ke := event.(*dom.KeyboardEvent); {
// Escape.
case ke.KeyCode == 27 && !ke.Repeat && !ke.CtrlKey && !ke.AltKey && !ke.MetaKey && !ke.ShiftKey:
if strings.TrimPrefix(dom.GetWindow().Location().Hash, "#") == "" {
return
}
setFragment("")
highlight(nil)
ke.PreventDefault()
}
})
// Jump to desired hash slightly after page loads (override browser's default hash jumping).
go func() {
// This needs to be delayed, or else it "happens too early".
time.Sleep(time.Millisecond)
processHash()
}()
}
func processHash() {
// Scroll to hash target.
targetID := strings.TrimPrefix(dom.GetWindow().Location().Hash, "#")
target, ok := document.GetElementByID(targetID).(dom.HTMLElement)
if ok {
centerWindowOn(target)
}
highlight(target)
}
// AnchorScroll scrolls window to target that is pointed by fragment of href of given anchor element.
// It must point to a valid target.
func AnchorScroll(anchor dom.HTMLElement, e dom.Event) {
url, err := url.Parse(anchor.(*dom.HTMLAnchorElement).Href)
if err != nil {
// Should never happen if AnchorScroll is used correctly.
panic(fmt.Errorf("AnchorScroll: url.Parse: %v", err))
}
targetID := url.Fragment
target := document.GetElementByID(targetID).(dom.HTMLElement)
setFragment(targetID)
// TODO: Decide if it's better to do this or not to.
centerWindowOn(target)
highlight(target)
e.PreventDefault()
}
// highlight highlights the selected element by giving it a "hash-selected" class.
// target can be nil to highlight nothing.
func highlight(target dom.HTMLElement) {
// Clear all past highlights.
for _, e := range document.GetElementsByClassName("hash-selected") {
e.Class().Remove("hash-selected")
}
// Highlight target, if any.
if target == nil {
return
}
target.Class().Add("hash-selected")
}
// centerWindowOn scrolls window so that (the middle of) target is in the middle of window.
func centerWindowOn(target dom.HTMLElement) {
windowHalfHeight := dom.GetWindow().InnerHeight() / 2
targetHalfHeight := target.OffsetHeight() / 2
if targetHalfHeight > float64(windowHalfHeight)*0.8 { // Prevent top of target from being offscreen.
targetHalfHeight = float64(windowHalfHeight) * 0.8
}
dom.GetWindow().ScrollTo(dom.GetWindow().ScrollX(), int(offsetTopRoot(target)+targetHalfHeight)-windowHalfHeight)
}
// offsetTopRoot returns the offset top of element e relative to root element.
func offsetTopRoot(e dom.HTMLElement) float64 {
var offsetTopRoot float64
for ; e != nil; e = e.OffsetParent() {
offsetTopRoot += e.OffsetTop()
}
return offsetTopRoot
}
// setFragment sets current page URL fragment to hash. The leading '#' shouldn't be included.
func setFragment(hash string) {
url := windowLocation
url.Fragment = hash
// TODO: dom.GetWindow().History().ReplaceState(...), blocked on https://github.com/dominikh/go-js-dom/issues/41.
js.Global.Get("window").Get("history").Call("replaceState", nil, nil, url.String())
}
var windowLocation = func() url.URL {
url, err := url.Parse(dom.GetWindow().Location().Href)
if err != nil {
// We don't expect this can ever happen, so treat it as an internal error if it does.
panic(fmt.Errorf("internal error: parsing window.location.href as URL failed: %v", err))
}
return *url
}()