-
Notifications
You must be signed in to change notification settings - Fork 14
/
Pages.elm
359 lines (294 loc) · 10.2 KB
/
Pages.elm
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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
module Pages exposing (main)
{-| Animated page transitions!
This example is meant to show a few things.
1. That page transitions are just like animating any other state, we'll just create an `Animator.Timeline Page` and animate with that.
2. How to use CSS keyframes by using the `Animator.Css` module
3. How to handle routing so that the url changes as your transition.
-}
import Animator
import Animator.Css
import Animator.Inline
import Browser
import Browser.Events
import Browser.Navigation
import Color
import Html exposing (..)
import Html.Attributes as Attr
import Html.Events as Events
import Time
import Url
import Url.Builder
import Url.Parser exposing ((</>))
{-| -}
type alias Model =
{ page : Animator.Timeline Page
, navKey : Browser.Navigation.Key
, needsUpdate : Bool
}
main =
Browser.application
{ init =
\() url navKey ->
let
initialPage =
Url.Parser.parse urlParser url
|> Maybe.withDefault NotFound
in
( { page = Animator.init initialPage
, navKey = navKey
, needsUpdate = False
}
, Cmd.none
)
, view = view
, update = update
, subscriptions =
\model ->
Sub.batch
[ animator
|> Animator.toSubscription Tick model
]
, onUrlRequest = ClickedLink
, onUrlChange = UrlChanged
}
{- URL Handling -}
type Page
= Home
| About
| Blog
| NotFound
urlParser : Url.Parser.Parser (Page -> a) a
urlParser =
Url.Parser.oneOf
[ Url.Parser.map Home Url.Parser.top
, Url.Parser.map Blog (Url.Parser.s "blog")
, Url.Parser.map About (Url.Parser.s "about")
]
pageToUrl : Page -> String
pageToUrl page =
case page of
Home ->
Url.Builder.absolute [] []
About ->
Url.Builder.absolute [ "about" ] []
Blog ->
Url.Builder.absolute [ "blog" ] []
NotFound ->
Url.Builder.absolute [ "notfound" ] []
animator : Animator.Animator Model
animator =
Animator.animator
-- *NOTE* We're using `the Animator.Css.watching` instead of `Animator.watching`.
-- Instead of asking for a constant stream of animation frames, it'll only ask for one
-- and we'll render the entire css animation in that frame.
|> Animator.Css.watching .page
(\newPage model ->
{ model | page = newPage }
)
{- UPDATING -}
type Msg
= Tick Time.Posix
| ClickedLink Browser.UrlRequest
| UrlChanged Url.Url
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Tick newTime ->
( Animator.update newTime animator model
, Cmd.none
)
ClickedLink request ->
case request of
Browser.Internal url ->
-- Note - Ideally, starting a new animation with `toNewPage` would only happen in `UrlChanged`
-- which occurs immediately after this message if we use `Browser.Navigation.pushUrl`
--
-- However there seems to be a bug in elm where a subscription to animationFrame fails to fire
-- if we start a new animation just in `UrlChanged`.
-- Note, this seems to be a sepcial case with routing
( toNewPage url model
, Browser.Navigation.pushUrl model.navKey (Url.toString url)
)
Browser.External url ->
( model
, Browser.Navigation.load url
)
UrlChanged url ->
-- This should be te only place we need to use `toNewPage`. See above note.
( toNewPage url model
, Cmd.none
)
toNewPage : Url.Url -> Model -> Model
toNewPage url model =
let
newPage =
Url.Parser.parse urlParser url
|> Maybe.withDefault NotFound
in
{ model
| page =
model.page
-- full page animations involve moving some large stuff.
-- in that case using a slower duration than normal is a good place to start.
|> Animator.go Animator.verySlowly newPage
}
{- Actually viewing our pages! -}
view : Model -> Browser.Document Msg
view model =
{ title = "Animator - Page Transitions"
, body =
[ stylesheet
, div
[ Attr.class "root"
]
[ nav []
[ link Home "Home"
, link Blog "Blog"
, link About "About"
]
, div
[ Attr.class "page-row"
]
[ viewPage model.page
Home
{ title = "The home page"
, content = loremIpsum
}
, viewPage model.page
Blog
{ title = "Blog"
, content = loremIpsum
}
, viewPage model.page
About
{ title = "About"
, content = loremIpsum
}
]
]
]
}
viewPage : Animator.Timeline Page -> Page -> { title : String, content : Html msg } -> Html Msg
viewPage timeline page { title, content } =
let
wrapInLink html =
if Animator.current timeline == page then
html
else
Html.a
[ Attr.href (pageToUrl page)
, Attr.style "cursor" "pointer"
]
[ html ]
in
Animator.Css.div timeline
(pageAnimation page)
[ Attr.class "page" ]
[ Html.h2 [] [ Html.text title ]
, loremIpsum
]
|> wrapInLink
pageAnimation : Page -> List (Animator.Css.Attribute Page)
pageAnimation page =
[ Animator.Css.opacity <|
\currentPage ->
if currentPage == page then
Animator.at 1
else
Animator.at 0.4
, Animator.Css.transform <|
\currentPage ->
if currentPage == page then
Animator.Css.scale 5
else
Animator.Css.scale 1
-- Here we're animating a style that's not directly supported by elm-animator.
, Animator.Css.style "margin"
(\float ->
let
str =
String.fromFloat float
in
"0px " ++ str ++ "px"
)
(\currentPage ->
if currentPage == page then
Animator.at 1800
else
Animator.at 0
)
-- because we're zooming everywhere, we can adjust the border width so it still looks nice
-- even when the page thumbnail is small.
, Animator.Css.style "border-width"
(\float ->
String.fromFloat float ++ "px"
)
(\currentPage ->
if currentPage == page then
Animator.at 1
else
Animator.at 5
)
]
{- Less Exciting Stuff
Below here is some content and a stylesheet.
-}
loremIpsum : Html msg
loremIpsum =
Html.div []
[ Html.div []
[ Html.text "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
]
, Html.div []
[ Html.text "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of \"de Finibus Bonorum et Malorum\" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, \"Lorem ipsum dolor sit amet..\", comes from a line in section 1.10.32."
]
, Html.div []
[ Html.text "The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from \"de Finibus Bonorum et Malorum\" by Cicero are also reproduced in their exact original form, accompanied by English versions from the 1914 translation by H. Rackham."
]
]
link : Page -> String -> Html msg
link page label =
Html.a
[ Attr.href (pageToUrl page)
, Attr.style "margin-right" "12px"
]
[ Html.text label ]
stylesheet : Html msg
stylesheet =
Html.node "style"
[]
[ text """@import url('https://fonts.googleapis.com/css?family=Roboto&display=swap');
a {
text-decoration: none;
color: black;
}
a:visited {
text-decoration: none;
color: black;
}
.root {
width: 100%;
height: 1000px;
font-size: 16px;
user-select: none;
padding: 50px;
font-family: 'Roboto', sans-serif;
}
.page-row {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
padding: 100px;
transform: scale(0.2);
}
.page {
width: 500px;
padding: 48px;
border: 1px solid black;
border-radius: 2px;
flex-shrink: 0;
background-color: white;
}
"""
]