1
0
Fork 0
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:
wryk 2023-03-02 05:34:36 +01:00
parent 4bf44d8a90
commit 45d65e1fde
6 changed files with 137 additions and 68 deletions

View file

@ -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
View file

@ -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",

View file

@ -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",

View file

@ -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 -->

View file

@ -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>
à lappel de lensemble des organisations syndicales, le 7 mars à lappel de lensemble des organisations syndicales, le 7 mars
<strong class="block counter text-big text-bold" v-text="formattedCount">148&nbsp;563</strong> <strong class="block counter text-big text-bold" x-bind="counter">148&nbsp;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&nbsp;?</h2> <h2>Et vous&nbsp;?</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>

View file

@ -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
})
} }