mirror of
https://github.com/GuerillaStudio/compteur-de-greve.git
synced 2024-12-18 05:41:56 +00:00
lower browser req, use alpine, animate counter
This commit is contained in:
parent
4bf44d8a90
commit
45d65e1fde
6 changed files with 137 additions and 68 deletions
|
@ -4,6 +4,11 @@ module.exports = function (eleventyConfig) {
|
||||||
eleventyConfig.addPlugin(eleventySass)
|
eleventyConfig.addPlugin(eleventySass)
|
||||||
|
|
||||||
eleventyConfig.setServerPassthroughCopyBehavior("passthrough")
|
eleventyConfig.setServerPassthroughCopyBehavior("passthrough")
|
||||||
|
|
||||||
|
eleventyConfig.addPassthroughCopy({
|
||||||
|
"node_modules/alpinejs/dist/cdn.min.js": "js/alpine.js"
|
||||||
|
})
|
||||||
|
|
||||||
eleventyConfig.addPassthroughCopy("src/js/**/*.js")
|
eleventyConfig.addPassthroughCopy("src/js/**/*.js")
|
||||||
eleventyConfig.addPassthroughCopy({ "static": "/" })
|
eleventyConfig.addPassthroughCopy({ "static": "/" })
|
||||||
|
|
||||||
|
|
24
package-lock.json
generated
24
package-lock.json
generated
|
@ -10,7 +10,8 @@
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@11ty/eleventy": "^2.0.0",
|
"@11ty/eleventy": "^2.0.0",
|
||||||
"@11tyrocks/eleventy-plugin-sass-lightningcss": "^1.0.0"
|
"@11tyrocks/eleventy-plugin-sass-lightningcss": "^1.0.0",
|
||||||
|
"alpinejs": "^3.11.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"stylelint": "^15.2.0",
|
"stylelint": "^15.2.0",
|
||||||
|
@ -425,6 +426,19 @@
|
||||||
"integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==",
|
"integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@vue/reactivity": {
|
||||||
|
"version": "3.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.1.5.tgz",
|
||||||
|
"integrity": "sha512-1tdfLmNjWG6t/CsPldh+foumYFo3cpyCHgBYQ34ylaMsJ+SNHQ1kApMIa8jN+i593zQuaw3AdWH0nJTARzCFhg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/shared": "3.1.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vue/shared": {
|
||||||
|
"version": "3.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz",
|
||||||
|
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA=="
|
||||||
|
},
|
||||||
"node_modules/a-sync-waterfall": {
|
"node_modules/a-sync-waterfall": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz",
|
||||||
|
@ -457,6 +471,14 @@
|
||||||
"url": "https://github.com/sponsors/epoberezkin"
|
"url": "https://github.com/sponsors/epoberezkin"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/alpinejs": {
|
||||||
|
"version": "3.11.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.11.1.tgz",
|
||||||
|
"integrity": "sha512-0Y+4WKQcEZrvpfS98qeSOXCPXFPorULQ+1hc8lQrx+1HHzkUofD4HzjTfz+wimA5tSsGnpXz/SoF2P9saiXZCw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/reactivity": "~3.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ansi-regex": {
|
"node_modules/ansi-regex": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||||
|
|
|
@ -13,7 +13,8 @@
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@11ty/eleventy": "^2.0.0",
|
"@11ty/eleventy": "^2.0.0",
|
||||||
"@11tyrocks/eleventy-plugin-sass-lightningcss": "^1.0.0"
|
"@11tyrocks/eleventy-plugin-sass-lightningcss": "^1.0.0",
|
||||||
|
"alpinejs": "^3.11.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"stylelint": "^15.2.0",
|
"stylelint": "^15.2.0",
|
||||||
|
|
|
@ -6,10 +6,13 @@
|
||||||
<title>{{ title }}</title>
|
<title>{{ title }}</title>
|
||||||
<link rel="stylesheet" href="{{ '/css/style.css' | url }}" />
|
<link rel="stylesheet" href="{{ '/css/style.css' | url }}" />
|
||||||
<link rel="preload" href="{{ '/css/style.css' | url }}" as="style" />
|
<link rel="preload" href="{{ '/css/style.css' | url }}" as="style" />
|
||||||
<style>[v-cloak] { display: none; }</style>
|
<style>[x-cloak] { display: none !important; }</style>
|
||||||
<script type="module" src="{{ '/js/script.js' | url }}"></script>
|
<script src="{{ '/js/alpine.js' | url }}" defer></script>
|
||||||
|
<link rel="preload" href="{{ '/js/alpine.js' | url }}" as="script" />
|
||||||
|
<script src="{{ '/js/script.js' | url }}"></script>
|
||||||
<link rel="preload" href="{{ '/js/script.js' | url }}" as="script" />
|
<link rel="preload" href="{{ '/js/script.js' | url }}" as="script" />
|
||||||
|
|
||||||
|
|
||||||
{% include 'preload-assets.njk' %}
|
{% include 'preload-assets.njk' %}
|
||||||
{% include 'favicons.njk' %}
|
{% include 'favicons.njk' %}
|
||||||
<!-- TODO: REMOVE IN PROD, but safer to have it for now -->
|
<!-- TODO: REMOVE IN PROD, but safer to have it for now -->
|
||||||
|
|
|
@ -2,14 +2,14 @@
|
||||||
layout: base.njk
|
layout: base.njk
|
||||||
title: Compteur de grève
|
title: Compteur de grève
|
||||||
---
|
---
|
||||||
<section class="banner" v-scope="App({ initialCount: 148563 })" @vue:mounted="mounted" @vue:unmounted="unmounted">
|
<section class="banner" x-data="counter" data-initial-count="148563">
|
||||||
<div class="grid grid-2 gap">
|
<div class="grid grid-2 gap">
|
||||||
<article class="text-center">
|
<article class="text-center">
|
||||||
<h1 class="visually-hidden">Compteur de Grève</h1>
|
<h1 class="visually-hidden">Compteur de Grève</h1>
|
||||||
<p>
|
<p>
|
||||||
<span class="color-3">Contre la réforme des retraites,</span><br>
|
<span class="color-3">Contre la réforme des retraites,</span><br>
|
||||||
à l’appel de l’ensemble des organisations syndicales, le 7 mars
|
à l’appel de l’ensemble des organisations syndicales, le 7 mars
|
||||||
<strong class="block counter text-big text-bold" v-text="formattedCount">148 563</strong>
|
<strong class="block counter text-big text-bold" x-bind="counter">148 563</strong>
|
||||||
<span class="separator">personnes seront</span>
|
<span class="separator">personnes seront</span>
|
||||||
<em class="block text-big text-bold">en grève<span class="visually-hidden"> !</span></em>
|
<em class="block text-big text-bold">en grève<span class="visually-hidden"> !</span></em>
|
||||||
</p>
|
</p>
|
||||||
|
@ -19,14 +19,14 @@ title: Compteur de grève
|
||||||
<h2>Et vous ?</h2>
|
<h2>Et vous ?</h2>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div>
|
<div>
|
||||||
<form action="/" method="post" v-if="!participating" @submit="submit">
|
<form action="/" method="post" x-bind="form">
|
||||||
<button class="btn w100 mb15" v-bind:disabled="loading">Je fais grève</button>
|
<button class="btn w100 mb15" x-bind="button">Je fais grève</button>
|
||||||
</form>
|
</form>
|
||||||
<div class="success" v-cloak v-if="participating">
|
<div class="success" x-bind="thanks" x-cloak>
|
||||||
<strong class="block text-bold">Merci pour votre participation ! ✨</strong>
|
<strong class="block text-bold">Merci pour votre participation ! ✨</strong>
|
||||||
↓ Nous avons <strong>besoin de vous</strong> pour faire du <em class="text-bold">7 mars</em> une journée de mobilisation qui restera dans les mémoires
|
↓ Nous avons <strong>besoin de vous</strong> pour faire du <em class="text-bold">7 mars</em> une journée de mobilisation qui restera dans les mémoires
|
||||||
</div>
|
</div>
|
||||||
<div class="error" v-cloak v-if="error">Message d'erreur à faire</div>
|
<div class="error" x-bind="error" x-cloak>Message d'erreur à faire</div>
|
||||||
</div>
|
</div>
|
||||||
<a href="#" class="btn btn--secondary w100 mb15">Je partage le compteur</a>
|
<a href="#" class="btn btn--secondary w100 mb15">Je partage le compteur</a>
|
||||||
<a href="#" class="btn btn--ghost w100 mb15">En savoir plus</a>
|
<a href="#" class="btn btn--ghost w100 mb15">En savoir plus</a>
|
||||||
|
|
122
src/js/script.js
122
src/js/script.js
|
@ -1,69 +1,105 @@
|
||||||
import { createApp } from "https://unpkg.com/petite-vue@0.4.1/dist/petite-vue.es.js"
|
|
||||||
|
|
||||||
|
|
||||||
let internalCount = 0
|
let internalCount = 0
|
||||||
|
|
||||||
createApp({
|
document.addEventListener('alpine:init', () => {
|
||||||
App
|
Alpine.data('counter', () => ({
|
||||||
}).mount(document.body)
|
count: null,
|
||||||
|
|
||||||
function App({ initialCount }) {
|
|
||||||
internalCount = initialCount
|
|
||||||
|
|
||||||
return {
|
|
||||||
count: initialCount,
|
|
||||||
|
|
||||||
get formattedCount() {
|
|
||||||
return this.count.toString().replace(/\B(?=(\d{3})+(?!\d))/g, " ")
|
|
||||||
},
|
|
||||||
|
|
||||||
participating: false,
|
participating: false,
|
||||||
error: false,
|
get notParticipating() {
|
||||||
|
return !this.participating
|
||||||
|
},
|
||||||
|
|
||||||
|
errored: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
|
|
||||||
async submit(event) {
|
init() {
|
||||||
|
this.count = parseInt(this.$el.dataset.initialCount)
|
||||||
|
internalCount = this.count
|
||||||
|
|
||||||
|
subscribeCount((newCount) => this.count = newCount)
|
||||||
|
|
||||||
|
this.$watch("count", (current, previous) => {
|
||||||
|
animateRange(150, previous, current, (value) => {
|
||||||
|
this.$refs.counter.textContent = formatNumber(value)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
counter: {
|
||||||
|
["x-ref"]: "counter",
|
||||||
|
},
|
||||||
|
|
||||||
|
button: {
|
||||||
|
["x-bind:disabled"]: "loading"
|
||||||
|
},
|
||||||
|
|
||||||
|
thanks: {
|
||||||
|
["x-show"]: "participating",
|
||||||
|
["x-transition"]: null
|
||||||
|
},
|
||||||
|
|
||||||
|
error: {
|
||||||
|
["x-show"]: "errored",
|
||||||
|
["x-transition"]: null
|
||||||
|
},
|
||||||
|
|
||||||
|
form: {
|
||||||
|
["x-show"]: "notParticipating",
|
||||||
|
["x-transition"]: null,
|
||||||
|
["x-on:submit"](event) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
try {
|
|
||||||
this.loading = true
|
this.loading = true
|
||||||
this.error = false
|
this.errored = false
|
||||||
|
|
||||||
this.count = await incrementCount()
|
incrementCount().then(() => {
|
||||||
this.participating = true
|
this.participating = true
|
||||||
} catch (error) {
|
}).catch((error) => {
|
||||||
this.error = true
|
this.errored = true
|
||||||
console.log(error)
|
console.log(error)
|
||||||
} finally {
|
|
||||||
|
}).finally(() => {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
unsubscribeCount: null,
|
function animateRange(duration, start, end, callback) {
|
||||||
|
let startTimestamp = null;
|
||||||
|
|
||||||
mounted() {
|
const step = (timestamp) => {
|
||||||
this.unsubscribeCount = subscribeCount((newCount) => this.count = newCount)
|
if (!startTimestamp) {
|
||||||
},
|
startTimestamp = timestamp
|
||||||
|
};
|
||||||
|
|
||||||
unmounted() {
|
const progress = Math.min((timestamp - startTimestamp) / duration, 1)
|
||||||
if (this.unsubscribeCount) {
|
callback(Math.floor(progress * (end - start) + start))
|
||||||
this.unsubscribeCount()
|
|
||||||
this.countSubcriber = null
|
if (progress < 1) {
|
||||||
|
requestAnimationFrame(step)
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
|
|
||||||
|
requestAnimationFrame(step);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatNumber(number) {
|
||||||
|
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
function subscribeCount(onCount) {
|
function subscribeCount(onCount) {
|
||||||
let unsubscribed = false
|
let unsubscribed = false
|
||||||
let timeoutId = null
|
let timeoutId = null
|
||||||
|
|
||||||
async function execute() {
|
function execute() {
|
||||||
const count = await fetchCount()
|
fetchCount().then(newCount => {
|
||||||
|
|
||||||
if (!unsubscribed) {
|
if (!unsubscribed) {
|
||||||
timeoutId = setTimeout(execute, 2000)
|
timeoutId = setTimeout(execute, 2000)
|
||||||
onCount(count)
|
onCount(newCount)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
execute()
|
execute()
|
||||||
|
@ -80,21 +116,23 @@ function subscribeCount(onCount) {
|
||||||
|
|
||||||
|
|
||||||
// fake api
|
// fake api
|
||||||
async function fetchCount() {
|
function fetchCount() {
|
||||||
await wait(1000)
|
return wait(1000).then(() => {
|
||||||
internalCount += randomInt(0, 9)
|
internalCount += randomInt(0, 9)
|
||||||
return internalCount
|
return internalCount
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function incrementCount() {
|
function incrementCount() {
|
||||||
await wait(1000)
|
return wait(1000).then(() => {
|
||||||
|
|
||||||
if (!randomInt(0, 1)) {
|
if (!randomInt(0, 1)) {
|
||||||
throw new Error()
|
throw new Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
internalCount += 1 + randomInt(0, 9)
|
internalCount += 1 + randomInt(0, 9)
|
||||||
return internalCount
|
return internalCount
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue