Skip to content

Commit aa2ab34

Browse files
authored
Merge pull request #292 from BeAPI/feature/basic-animations-alt
Feature/basic animations alt
2 parents c7eb7c6 + 1f52b84 commit aa2ab34

File tree

7 files changed

+357
-4
lines changed

7 files changed

+357
-4
lines changed

header.php

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
<!DOCTYPE html>
2-
<html class="no-js" <?php language_attributes(); ?>>
2+
<html class="no-js no-js-animation" <?php language_attributes(); ?>>
33
<head>
44
<script type="text/javascript">
55
//<![CDATA[
66
(function(){
7-
var c = document.documentElement.className;
8-
c = c.replace(/no-js/, 'js');
9-
document.documentElement.className = c;
7+
function replaceHtmlClass(regexp, str) {
8+
var h = document.documentElement;
9+
h.className = h.className.replace(regexp, str);
10+
}
11+
12+
replaceHtmlClass(/no-js/, 'js');
13+
14+
if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
15+
replaceHtmlClass(/no-js-animation/, 'js-animation');
16+
}
1017
})();
1118
//]]>
1219
</script>

index.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@
3838

3939
<h6>Titre de niveau 6</h6>
4040

41+
<h2>Fusce a quam. Donec mollis hendrerit risus. Nam commodo suscipit quam. Maecenas nec odio et ante tincidunt
42+
tempus. Praesent egestas tristique nibh.</h2>
43+
4144
<p>Fusce a quam. Donec mollis hendrerit risus. Nam commodo suscipit quam. Maecenas nec odio et ante tincidunt
4245
tempus. Praesent egestas tristique nibh.</p>
4346

@@ -174,6 +177,24 @@ class="has-black-background-color has-background-dim-100 wp-block-cover__gradien
174177
</div>
175178
</div>
176179

180+
<div class="container" style="padding: 100px 0">
181+
<h1 class="js-animation-title">Exemples d'animation</h1>
182+
<p class="js-animation-opacity">Fusce a quam. Donec mollis hendrerit risus. Nam commodo suscipit quam. Maecenas nec odio et ante tincidunt
183+
tempus. Praesent egestas tristique nibh.</p>
184+
185+
<ul style="display: flex; justify-content: space-between; list-style: none; margin-top: 50px">
186+
<li class="js-animation-translation" style="width: 32%">
187+
<div style="background: #000; height: 450px;"></div>
188+
</li>
189+
<li class="js-animation-translation" style="width: 32%">
190+
<div style="background: #000; height: 450px;"></div>
191+
</li>
192+
<li class="js-animation-translation" style="width: 32%">
193+
<div style="background: #000; height: 450px;"></div>
194+
</li>
195+
</ul>
196+
</div>
197+
177198
<div class="container" style="padding: 100px 0">
178199
<form>
179200
<div style="margin-bottom: 32px">

src/js/classes/Animation.js

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
import AbstractDomElement from './AbstractDomElement'
2+
import { ScrollObserver, SplittedText } from 'oneloop.js'
3+
import noop from '../utils/noop'
4+
5+
// ----
6+
// shared variables
7+
// ----
8+
const instances = []
9+
let scrollObserver
10+
11+
// ----
12+
// class Animation
13+
// ----
14+
class Animation extends AbstractDomElement {
15+
constructor(element, options) {
16+
const instance = super(element, options)
17+
18+
// avoid double init :
19+
if (!instance.isNewInstance()) {
20+
return instance
21+
}
22+
23+
const that = this
24+
const el = this._element
25+
const s = this._settings
26+
const start = getValue(el, s.start)
27+
const end = getValue(el, s.end)
28+
const callbacksSharedData = {}
29+
30+
this._isVisible = false
31+
this._callbacksSharedData = callbacksSharedData
32+
33+
// add to instances
34+
instances.push(this)
35+
36+
// init scrollObserver
37+
if (!scrollObserver) {
38+
scrollObserver = new ScrollObserver()
39+
}
40+
41+
// add animation class
42+
el.classList.add(s.animationClass)
43+
44+
// intialize callback
45+
s.onInit(el, scrollObserver.getScrollInfos(), callbacksSharedData)
46+
47+
// add element to scrollObserver
48+
scrollObserver.observe(el, {
49+
onVisible: function (scrollInfos, percentRTE) {
50+
if (percentRTE >= start && percentRTE <= end && !that._isVisible) {
51+
// show element
52+
that._isVisible = true
53+
s.onShow(el, scrollInfos, callbacksSharedData)
54+
el.classList.add(s.visibleClass)
55+
56+
// destroy if playOnce = true
57+
if (s.playOnce) {
58+
that.destroy(el, scrollInfos, callbacksSharedData)
59+
}
60+
} else if ((percentRTE < start || (percentRTE > end && s.hideOnReachEnd)) && that._isVisible) {
61+
// hide element
62+
that._isVisible = false
63+
s.onHide(el, scrollInfos, callbacksSharedData)
64+
el.classList.remove(s.visibleClass)
65+
}
66+
},
67+
})
68+
}
69+
70+
isVisible() {
71+
return this._isVisible
72+
}
73+
74+
destroy() {
75+
const el = this._element
76+
const s = this._settings
77+
const index = instances.indexOf(this)
78+
const callbacksSharedData = this._callbacksSharedData
79+
let scrollInfos
80+
81+
if (index === -1) {
82+
return
83+
}
84+
85+
super.destroy()
86+
87+
scrollInfos = scrollObserver.getScrollInfos()
88+
instances.splice(index, 1)
89+
90+
if (s.showOnDestroy) {
91+
s.onShow(el, scrollInfos, callbacksSharedData)
92+
el.classList.add(s.visibleClass)
93+
}
94+
95+
scrollObserver.unobserve(el)
96+
97+
if (!scrollObserver.hasEntry) {
98+
scrollObserver.destroy()
99+
}
100+
101+
this._settings.onDestroy(el, scrollInfos, callbacksSharedData)
102+
}
103+
104+
static destroy() {
105+
while (instances.length) {
106+
instances[0].destroy()
107+
}
108+
}
109+
}
110+
111+
// ----
112+
// defaults
113+
// ----
114+
Animation.defaults = {
115+
// wanted animation, the class will be added on the element if is not already on it
116+
animationClass: 'js-animation-opacity',
117+
// class added when the element is in the start/end range
118+
visibleClass: 'is-visible',
119+
// start (relative to bottom of the screen), can be a float, a function (element) {} or an array of two values (range) []
120+
start: 0.25,
121+
// end (relative to bottom of the screen), can be a float, a function (element) {} or an array of two values (range) []
122+
end: 0.75,
123+
// if true, the instance will be destroyed after the element is visible
124+
playOnce: false,
125+
// if true, remove the visible class when the element reach the end paramter value
126+
hideOnReachEnd: false,
127+
// if true, set the element visible on destroy whatever the current scroll value
128+
showOnDestroy: true,
129+
// for each callback : function (element, scrollInfos, callbacksSharedData)
130+
onInit: noop,
131+
onShow: noop,
132+
onHide: noop,
133+
onDestroy: noop,
134+
}
135+
136+
// ----
137+
// utils
138+
// ----
139+
function getValue(element, value) {
140+
let rt = value
141+
142+
if (typeof value === 'function') {
143+
rt = value(element)
144+
} else if (Array.isArray(value)) {
145+
rt = Math.random() * (value[1] - value[0]) + value[0]
146+
}
147+
148+
return rt
149+
}
150+
151+
// ----
152+
// presets
153+
// ----
154+
Animation.preset = {
155+
'.js-animation .js-animation-opacity': undefined,
156+
'.js-animation .js-animation-translation': {
157+
animationClass: 'js-animation-translation',
158+
start: [0.2, 0.25],
159+
end: [0.75, 0.8],
160+
onInit: function (el, scrollInfos, data) {
161+
data.translate = Math.round(Math.random() * 100 + 100) * -1
162+
el.children[0].style.transitionDuration = Math.random() * 0.75 + 0.75 + 's'
163+
},
164+
onShow: function (el, scrollInfos, data) {
165+
el.children[0].style.transform = 'translateY(' + scrollInfos.directionY * data.translate + 'px)'
166+
},
167+
onHide: function (el, scrollInfos, data) {
168+
el.children[0].style.transform = 'translateY(' + scrollInfos.directionY * data.translate + 'px)'
169+
},
170+
},
171+
'.js-animation .js-animation-title': {
172+
animationClass: 'js-animation-title',
173+
onInit: function (el, scrollInfos, data) {
174+
document.fonts.ready.then(function () {
175+
data.splittedText = new SplittedText(el, {
176+
byLine: true,
177+
lineWrapper: function (line) {
178+
return '<span class="st-line"><span>' + line + '</span></span>'
179+
},
180+
})
181+
182+
const children = el.children
183+
const length = children.length
184+
let i
185+
186+
if (length > 1) {
187+
for (i = 0; i < length; i++) {
188+
children[i].children[0].style.transitionDelay = i / (length - 1) / 5 + 's'
189+
}
190+
}
191+
192+
el.classList.add('is-ready')
193+
})
194+
},
195+
onDestroy: function (el, scrollInfos, data) {
196+
data.splittedText.destroy()
197+
},
198+
},
199+
}
200+
201+
// ----
202+
// presets
203+
// ----
204+
Animation.initFromPreset()
205+
206+
// ----
207+
// export
208+
// ----
209+
export default Animation

src/js/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'what-input'
55
import './classes/ScrollDirection'
66
import './classes/ButtonSeoClick'
77
import './classes/Header'
8+
import './classes/Animation'
89

910
/**
1011
* LazySizes configuration

src/js/utils/noop.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default function () {}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/**
2+
* js-animation
3+
*
4+
* How it work :
5+
* .js-animation and .no-js-animation are toggled on html element
6+
* Animation are triggered in javascript if prefered reduce motion != reduce
7+
*
8+
* How to use :
9+
* You can use the class directly on the dom element : <h2 class="js-animation-opacity">
10+
*
11+
* Or
12+
*
13+
* extend your element with the init class and add it to Animation.js preset
14+
* (use js-animation class accordingly to user preference)
15+
* .js-animation h2 {
16+
* @extend %js-animation-opacity-init;
17+
* }
18+
*
19+
* in the js file Animation.js, add you selector
20+
* Animation.preset = {
21+
* '.js-animation .js-animation-opacity, .js-animation h2': ...
22+
*
23+
* the class js-animation-opacity will be added by the js so only the init styles are required
24+
*
25+
*/
26+
// ====
27+
// Animation abstract init class
28+
// ====
29+
// ----
30+
// opacity animation
31+
// ----
32+
%js-animation-opacity-init {
33+
opacity: 0;
34+
transition: opacity .5s;
35+
}
36+
// ----
37+
// vertical translation + opacity animation
38+
// ----
39+
%js-animation-translation-init {
40+
> * {
41+
opacity: 0;
42+
transition: opacity .5s, transform .5s $ease-in-out-expo;
43+
transform: translateY(100px);
44+
}
45+
}
46+
// ----
47+
// title animation
48+
// ----
49+
%js-animation-title-init {
50+
visibility: hidden;
51+
}
52+
53+
// ====
54+
// css animation
55+
// ====
56+
.js-animation {
57+
$el: &;
58+
59+
// ----
60+
// opacity animation
61+
// ----
62+
#{$el}-opacity {
63+
@extend %js-animation-opacity-init;
64+
65+
&.is-visible {
66+
opacity: 1;
67+
}
68+
}
69+
70+
// ----
71+
// title animation
72+
// ----
73+
#{$el}-title {
74+
@extend %js-animation-title-init;
75+
76+
.st-line {
77+
display: inline-block;
78+
overflow: hidden;
79+
80+
> span {
81+
display: inline-block;
82+
transition: transform .75s $ease-out-expo;
83+
transform: translateY(150%);
84+
}
85+
}
86+
87+
&.is-ready {
88+
visibility: visible;
89+
}
90+
91+
&.is-visible {
92+
.st-line {
93+
> span {
94+
transform: translateY(0);
95+
}
96+
}
97+
}
98+
}
99+
100+
// ----
101+
// vertical translation + opacity animation
102+
// ----
103+
#{$el}-translation {
104+
@extend %js-animation-translation-init;
105+
106+
&.is-visible {
107+
> * {
108+
opacity: 1;
109+
transform: translateY(0) !important;
110+
}
111+
}
112+
}
113+
}

src/scss/04-utilities/utilities.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55
@import "./palette";
66
@import "./container";
77
@import "./sr-only";
8+
@import "./js-animation";

0 commit comments

Comments
 (0)