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.setServerPassthroughCopyBehavior("passthrough")
|
||||
|
||||
eleventyConfig.addPassthroughCopy({
|
||||
"node_modules/alpinejs/dist/cdn.min.js": "js/alpine.js"
|
||||
})
|
||||
|
||||
eleventyConfig.addPassthroughCopy("src/js/**/*.js")
|
||||
eleventyConfig.addPassthroughCopy({ "static": "/" })
|
||||
|
||||
|
|
24
package-lock.json
generated
24
package-lock.json
generated
|
@ -10,7 +10,8 @@
|
|||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@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": {
|
||||
"stylelint": "^15.2.0",
|
||||
|
@ -425,6 +426,19 @@
|
|||
"integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==",
|
||||
"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": {
|
||||
"version": "1.0.1",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
|
|
|
@ -13,7 +13,8 @@
|
|||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@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": {
|
||||
"stylelint": "^15.2.0",
|
||||
|
|
|
@ -6,10 +6,13 @@
|
|||
<title>{{ title }}</title>
|
||||
<link rel="stylesheet" href="{{ '/css/style.css' | url }}" />
|
||||
<link rel="preload" href="{{ '/css/style.css' | url }}" as="style" />
|
||||
<style>[v-cloak] { display: none; }</style>
|
||||
<script type="module" src="{{ '/js/script.js' | url }}"></script>
|
||||
<style>[x-cloak] { display: none !important; }</style>
|
||||
<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" />
|
||||
|
||||
|
||||
{% include 'preload-assets.njk' %}
|
||||
{% include 'favicons.njk' %}
|
||||
<!-- TODO: REMOVE IN PROD, but safer to have it for now -->
|
||||
|
|
|
@ -2,14 +2,14 @@
|
|||
layout: base.njk
|
||||
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">
|
||||
<article class="text-center">
|
||||
<h1 class="visually-hidden">Compteur de Grève</h1>
|
||||
<p>
|
||||
<span class="color-3">Contre la réforme des retraites,</span><br>
|
||||
à 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>
|
||||
<em class="block text-big text-bold">en grève<span class="visually-hidden"> !</span></em>
|
||||
</p>
|
||||
|
@ -19,14 +19,14 @@ title: Compteur de grève
|
|||
<h2>Et vous ?</h2>
|
||||
<div class="flex flex-col">
|
||||
<div>
|
||||
<form action="/" method="post" v-if="!participating" @submit="submit">
|
||||
<button class="btn w100 mb15" v-bind:disabled="loading">Je fais grève</button>
|
||||
<form action="/" method="post" x-bind="form">
|
||||
<button class="btn w100 mb15" x-bind="button">Je fais grève</button>
|
||||
</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>
|
||||
↓ 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 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>
|
||||
<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>
|
||||
|
|
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
|
||||
|
||||
createApp({
|
||||
App
|
||||
}).mount(document.body)
|
||||
|
||||
function App({ initialCount }) {
|
||||
internalCount = initialCount
|
||||
|
||||
return {
|
||||
count: initialCount,
|
||||
|
||||
get formattedCount() {
|
||||
return this.count.toString().replace(/\B(?=(\d{3})+(?!\d))/g, " ")
|
||||
},
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('counter', () => ({
|
||||
count: null,
|
||||
|
||||
participating: false,
|
||||
error: false,
|
||||
get notParticipating() {
|
||||
return !this.participating
|
||||
},
|
||||
|
||||
errored: 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()
|
||||
|
||||
try {
|
||||
this.loading = true
|
||||
this.error = false
|
||||
this.errored = false
|
||||
|
||||
this.count = await incrementCount()
|
||||
incrementCount().then(() => {
|
||||
this.participating = true
|
||||
} catch (error) {
|
||||
this.error = true
|
||||
}).catch((error) => {
|
||||
this.errored = true
|
||||
console.log(error)
|
||||
} finally {
|
||||
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
}))
|
||||
})
|
||||
|
||||
unsubscribeCount: null,
|
||||
function animateRange(duration, start, end, callback) {
|
||||
let startTimestamp = null;
|
||||
|
||||
mounted() {
|
||||
this.unsubscribeCount = subscribeCount((newCount) => this.count = newCount)
|
||||
},
|
||||
const step = (timestamp) => {
|
||||
if (!startTimestamp) {
|
||||
startTimestamp = timestamp
|
||||
};
|
||||
|
||||
unmounted() {
|
||||
if (this.unsubscribeCount) {
|
||||
this.unsubscribeCount()
|
||||
this.countSubcriber = null
|
||||
const progress = Math.min((timestamp - startTimestamp) / duration, 1)
|
||||
callback(Math.floor(progress * (end - start) + start))
|
||||
|
||||
if (progress < 1) {
|
||||
requestAnimationFrame(step)
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
requestAnimationFrame(step);
|
||||
}
|
||||
|
||||
function formatNumber(number) {
|
||||
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, " ")
|
||||
}
|
||||
|
||||
function subscribeCount(onCount) {
|
||||
let unsubscribed = false
|
||||
let timeoutId = null
|
||||
|
||||
async function execute() {
|
||||
const count = await fetchCount()
|
||||
|
||||
function execute() {
|
||||
fetchCount().then(newCount => {
|
||||
if (!unsubscribed) {
|
||||
timeoutId = setTimeout(execute, 2000)
|
||||
onCount(count)
|
||||
onCount(newCount)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
execute()
|
||||
|
@ -80,21 +116,23 @@ function subscribeCount(onCount) {
|
|||
|
||||
|
||||
// fake api
|
||||
async function fetchCount() {
|
||||
await wait(1000)
|
||||
function fetchCount() {
|
||||
return wait(1000).then(() => {
|
||||
internalCount += randomInt(0, 9)
|
||||
return internalCount
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
async function incrementCount() {
|
||||
await wait(1000)
|
||||
|
||||
function incrementCount() {
|
||||
return wait(1000).then(() => {
|
||||
if (!randomInt(0, 1)) {
|
||||
throw new Error()
|
||||
}
|
||||
|
||||
internalCount += 1 + randomInt(0, 9)
|
||||
return internalCount
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue