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

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

View file

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

View file

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

View file

@ -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>
à 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>
<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&nbsp;?</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>

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