From 6d842eefd0831c89f3be60e85dba91304fd3d00d Mon Sep 17 00:00:00 2001 From: Serghei Iakovlev Date: Sun, 24 Jul 2022 13:05:46 +0200 Subject: Provide 'Back to top' functionality --- CHANGELOG.md | 1 + assets/js/ed.js | 14 +++++ assets/sass/_customize.scss | 62 ++++++++++++++++++++-- assets/sass/_ed.scss | 2 +- assets/sass/_form-elements.scss | 6 +-- assets/sass/_mixins.scss | 53 ++++++++++++++---- exampleSite/config/_default/config.yaml | 2 +- i18n/en.toml | 6 +++ i18n/ru.toml | 6 +++ layouts/_default/baseof.html | 12 ++++- layouts/partials/head.html | 3 +- layouts/partials/scripts.html | 14 +++++ ...e.scss_f300667da4f5b5f84e1a9e0702b2fdde.content | 50 +++++++++++++++++ 13 files changed, 208 insertions(+), 23 deletions(-) create mode 100644 assets/js/ed.js create mode 100644 layouts/partials/scripts.html diff --git a/CHANGELOG.md b/CHANGELOG.md index d087505..e4bc6f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 (will be used for Schema.org) - Provide ability to mark links as external using svg icon - Provide minimal tag cloud +- Provide 'Back to top' functionality ### Changed diff --git a/assets/js/ed.js b/assets/js/ed.js new file mode 100644 index 0000000..dbea865 --- /dev/null +++ b/assets/js/ed.js @@ -0,0 +1,14 @@ +document.addEventListener('DOMContentLoaded', function () { + // 'Back to top' logic + const intersectionObserver = new IntersectionObserver(function(entries) { + const topBtn = document.querySelector('.top-of-site-link'); + if (topBtn === null) return; + + topBtn.dataset.visible = entries[0].boundingClientRect.y < 0; + }); + + const topAnchor = document.querySelector('#top-of-site-anchor'); + if (topAnchor !== null) { + intersectionObserver.observe(topAnchor); + } +}); diff --git a/assets/sass/_customize.scss b/assets/sass/_customize.scss index 085bb6f..82cfcf4 100644 --- a/assets/sass/_customize.scss +++ b/assets/sass/_customize.scss @@ -163,7 +163,7 @@ ul.pager { color: #fff; text-decoration: none; - @include border-radius (0 4px 4px 0); + @include border-radius(0 4px 4px 0); &:before { content: ""; @@ -188,8 +188,8 @@ ul.pager { height: 4px; background: #fff; - @include border-radius (2px); - @include box-shadow (-1px -1px 2px $text-color); + @include border-radius(2px); + @include box-shadow(-1px -1px 2px $text-color); } &:hover { @@ -211,8 +211,8 @@ a.external { margin-left: 4px; width: 10px; - @include mask-size (cover); - @include mask-image (url("/img/external-link.svg")); + @include mask-size(cover); + @include mask-image(url("/img/external-link.svg")); } } @@ -238,3 +238,55 @@ ul.tags-cloud { @include transition(0.3s); } } + +#top-of-site-anchor { + position: absolute; + width: 1px; + height: 1px; + top: 500px; + left: 0; +} + +@media (max-width: 800px){ + #top-of-site-anchor { + top: 46px; + } +} + +.top-of-site-link { + visibility: hidden; + opacity: 0; + text-decoration: none; + position: fixed; + bottom: 1.2rem; + right: 1.5rem; + z-index: 99; + + @include transition(.2s); + + &:hover { + text-decoration: none; + } + + svg { + @include filter(drop-shadow(0 2px 5px rgba(0, 0, 0, .3))); + } +} + +.top-of-site-link[data-visible=true] { + opacity: 1; + visibility: visible; +} + +.screen-reader-text { + position: absolute; + word-wrap: normal; + border: 0; + height: 1px; + width: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + + @include clip-path(inset(50%)); +} diff --git a/assets/sass/_ed.scss b/assets/sass/_ed.scss index 2f26ad1..b0870dc 100644 --- a/assets/sass/_ed.scss +++ b/assets/sass/_ed.scss @@ -25,7 +25,7 @@ */ * { - @include box-sizing (border-box); + @include box-sizing(border-box); } html, diff --git a/assets/sass/_form-elements.scss b/assets/sass/_form-elements.scss index 5948213..cbb6c08 100644 --- a/assets/sass/_form-elements.scss +++ b/assets/sass/_form-elements.scss @@ -24,7 +24,7 @@ } &:focus { - @include box-shadow (none); + @include box-shadow(none); outline: none; border-color: #5e5e5e; } @@ -73,13 +73,13 @@ form textarea:-moz-submit-invalid, form input[type="text"]:-moz-submit-invalid, form select:-moz-submit-invalid, form input:-moz-ui-invalid { - @include box-shadow (0 0 2px 1px #D64541!important); + @include box-shadow(0 0 2px 1px #D64541!important); } .form-item-error { .form-input, .form-textarea { - @include box-shadow (0 0 2px 1px #D64541!important); + @include box-shadow(0 0 2px 1px #D64541!important); } } diff --git a/assets/sass/_mixins.scss b/assets/sass/_mixins.scss index ae030d4..95e63e4 100644 --- a/assets/sass/_mixins.scss +++ b/assets/sass/_mixins.scss @@ -122,7 +122,7 @@ $break-desktop: 1600px; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Box Sizing // Sets how the total width and height of an element is calculated. // -// Usage: @include box-sizing (border-box); +// Usage: @include box-sizing(border-box); @mixin box-sizing ($value) { -webkit-box-sizing: $value; @@ -133,7 +133,7 @@ $break-desktop: 1600px; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Box Shadow // -// Usage: @include box-shadow (0 0 2px 1px #e21a23!important); +// Usage: @include box-shadow(0 0 2px 1px #e21a23!important); @mixin box-shadow ($args...) { -webkit-box-shadow: $args; @@ -143,7 +143,7 @@ $break-desktop: 1600px; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Border Radius // -// Usage: @include border-radius (10% 30% 50% 70%); +// Usage: @include border-radius(10% 30% 50% 70%); @mixin border-radius ($args...) { -webkit-border-radius: $args; @@ -155,7 +155,7 @@ $break-desktop: 1600px; // Sets whether flex items are forced onto one line or can wrap onto // multiple lines. // -// Usage: @include flex-wrap (wrap); +// Usage: @include flex-wrap(wrap); @mixin flex-wrap ($value) { -webkit-flex-wrap: $value; @@ -167,7 +167,7 @@ $break-desktop: 1600px; // Specifies how an element's fragments should be rendered when broken across // multiple lines, columns, or pages. // -// Usage: @include box-decoration-break (clone); +// Usage: @include box-decoration-break(clone); @mixin box-decoration-break ($value) { -webkit-box-decoration-break: $value; @@ -178,16 +178,47 @@ $break-desktop: 1600px; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Mask -// Usage: @include @include mask-image (url("/img/external-link.svg")); +// Usage: @include mask-image(url("/img/external-link.svg")); @mixin mask-image($value) { - -webkit-mask-image: $value; // Chrome, iOS, Safari - mask-image: $value; // None yet / Non-standard + -webkit-mask-image: $value; + mask-image: $value; } -// Usage: @include mask-size (cover); +// Usage: @include mask-size(cover); @mixin mask-size($value) { - -webkit-mask-size: $value; // Chrome, iOS, Safari - mask-size: $value; // None yet / Non-standard + -webkit-mask-size: $value; + mask-size: $value; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Filter +// Applies graphical effects like blur or color shift to an element. +// +// Usage: @include filter(drop-shadow(0 2px 5px rgba(0, 0, 0, .3))); + +@mixin filter($value) { + -webkit-filter: $value; + -moz-filter: $value; // editorconfig-checker-disable-line + -o-filter: $value; // editorconfig-checker-disable-line + filter: $value; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - clip-path +// Create a clipping region that sets what part of an element should be shown. +// +// Usage: @include clip-path( +// padding-box, +// circle(50px at 0 100px), +// // ... +// ); + +@mixin clip-path($clip-list...) { + // combine the passed transforms into a space separated list + $clip-list: join($clip-list, null, space); + + -webkit-clip-path: $clip-list; + -moz-clip-path: $clip-list; + -ms-clip-path: $clip-list; + clip-path: $clip-list; } diff --git a/exampleSite/config/_default/config.yaml b/exampleSite/config/_default/config.yaml index 9a6320c..930f608 100644 --- a/exampleSite/config/_default/config.yaml +++ b/exampleSite/config/_default/config.yaml @@ -38,7 +38,7 @@ taxonomies: # # Set `HUGO_ENV` environment variable or `site.Params.env` configuration # parameter to "production" to use Google Analytics. -googleAnalytics: 'xxx' +googleAnalytics: '' minify: # Do not minify XML files to avoid CDATA escape issues diff --git a/i18n/en.toml b/i18n/en.toml index 0c786b7..bbdff71 100644 --- a/i18n/en.toml +++ b/i18n/en.toml @@ -66,3 +66,9 @@ [tag_cloud] other = 'Tag Cloud' + +[back_to_top] + other = 'Back to top' + +[back_to_top_label] + other = 'Back to top of the page' diff --git a/i18n/ru.toml b/i18n/ru.toml index 40aba8c..abddef4 100644 --- a/i18n/ru.toml +++ b/i18n/ru.toml @@ -66,3 +66,9 @@ [tag_cloud] other = 'Облако тегов' + +[back_to_top] + other = 'Вернуться в начало' + +[back_to_top_label] + other = 'Вернуться в начало страницы' diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html index 4985ca3..5f095c2 100644 --- a/layouts/_default/baseof.html +++ b/layouts/_default/baseof.html @@ -1,7 +1,8 @@ - + {{ partial "head.html" . }} +
{{ if .Params.toc }} {{ partial "sidebar-toc.html" . }} {{ else }} @@ -23,5 +24,14 @@ + + + + {{ i18n "back_to_top" }} + + + + + diff --git a/layouts/partials/head.html b/layouts/partials/head.html index 934ccc9..036b26d 100644 --- a/layouts/partials/head.html +++ b/layouts/partials/head.html @@ -35,6 +35,7 @@ {{- template "partials/templates/twitter_cards.html" . }} {{- template "partials/templates/schema_json.html" . }} - {{- /* SEO */}} + {{- /* Scripts */}} + {{- partial "partials/scripts.html" . }} {{- partial "partials/seo/ga.html" . }} diff --git a/layouts/partials/scripts.html b/layouts/partials/scripts.html new file mode 100644 index 0000000..a42e492 --- /dev/null +++ b/layouts/partials/scripts.html @@ -0,0 +1,14 @@ +{{- $isProduction := (or (eq (getenv "HUGO_ENV") "production") (eq site.Params.env "production")) -}} + +{{- $scripts := slice -}} +{{- $scripts = $scripts | append (resources.Get "js/ed.js") -}} +{{- $scripts = $scripts | resources.Concat "js/common.js" -}} + +{{- $scripts = $scripts | js.Build (dict "format" "iife" "minify" $isProduction) -}} + +{{- if or (site.Params.assets.disable_fingerprinting) (not $isProduction) }} + +{{- else -}} + {{- $scripts = $scripts | fingerprint }} + +{{- end -}} diff --git a/resources/_gen/assets/scss/sass/style.scss_f300667da4f5b5f84e1a9e0702b2fdde.content b/resources/_gen/assets/scss/sass/style.scss_f300667da4f5b5f84e1a9e0702b2fdde.content index 69adea0..afc21d5 100644 --- a/resources/_gen/assets/scss/sass/style.scss_f300667da4f5b5f84e1a9e0702b2fdde.content +++ b/resources/_gen/assets/scss/sass/style.scss_f300667da4f5b5f84e1a9e0702b2fdde.content @@ -1075,6 +1075,56 @@ ul.tags-cloud { -o-transition: 0.3s; transition: 0.3s; } +#top-of-site-anchor { + position: absolute; + width: 1px; + height: 1px; + top: 500px; + left: 0; } + +@media (max-width: 800px) { + #top-of-site-anchor { + top: 46px; } } + +.top-of-site-link { + visibility: hidden; + opacity: 0; + text-decoration: none; + position: fixed; + bottom: 1.2rem; + right: 1.5rem; + z-index: 99; + -webkit-transition: 0.2s; + -moz-transition: 0.2s; + -ms-transition: 0.2s; + -o-transition: 0.2s; + transition: 0.2s; } + .top-of-site-link:hover { + text-decoration: none; } + .top-of-site-link svg { + -webkit-filter: drop-shadow(0 2px 5px rgba(0, 0, 0, 0.3)); + -moz-filter: drop-shadow(0 2px 5px rgba(0, 0, 0, 0.3)); + -o-filter: drop-shadow(0 2px 5px rgba(0, 0, 0, 0.3)); + filter: drop-shadow(0 2px 5px rgba(0, 0, 0, 0.3)); } + +.top-of-site-link[data-visible=true] { + opacity: 1; + visibility: visible; } + +.screen-reader-text { + position: absolute; + word-wrap: normal; + border: 0; + height: 1px; + width: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + -webkit-clip-path: inset(50%); + -moz-clip-path: inset(50%); + -ms-clip-path: inset(50%); + clip-path: inset(50%); } + /* Themes */ /* Red */ .theme-base-red .post-tags a { -- cgit v1.2.3