Produce and use webp pictures with different sizes
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
@ -4,7 +4,9 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build --modern",
|
||||
"build:assets": "vue-cli-service build --modern",
|
||||
"build:pictures": "scripty",
|
||||
"build": "yarn run build:assets && yarn run build:pictures",
|
||||
"test:unit": "LANG=en_US.UTF-8 LANGUAGE=en_US:en LC_ALL=en_US.UTF-8 vue-cli-service test:unit",
|
||||
"test:e2e": "vue-cli-service test:e2e",
|
||||
"lint": "vue-cli-service lint"
|
||||
@ -87,6 +89,7 @@
|
||||
"prettier-eslint": "^12.0.0",
|
||||
"sass": "^1.29.0",
|
||||
"sass-loader": "^10.0.1",
|
||||
"scripty": "^2.0.0",
|
||||
"typescript": "~4.1.2",
|
||||
"vue-cli-plugin-svg": "~0.1.3",
|
||||
"vue-i18n-extract": "^1.0.2",
|
||||
|
Before Width: | Height: | Size: 725 KiB After Width: | Height: | Size: 725 KiB |
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 1.8 MiB |
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 133 KiB |
90
js/scripts/build/pictures.sh
Executable file
@ -0,0 +1,90 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
|
||||
output_dir="../priv/static/img/pics"
|
||||
resolutions=(
|
||||
480
|
||||
1024
|
||||
1920
|
||||
)
|
||||
ignore=(
|
||||
homepage_background.png
|
||||
)
|
||||
|
||||
file_extension () {
|
||||
filename=$(basename -- "$file")
|
||||
echo "${filename##*.}"
|
||||
}
|
||||
|
||||
file_name () {
|
||||
filename=$(basename -- "$file")
|
||||
echo "${filename%.*}"
|
||||
}
|
||||
|
||||
convert_image () {
|
||||
name=$(file_name)
|
||||
extension=$(file_extension)
|
||||
res="$1w"
|
||||
output="$output_dir/$name-$res.$extension"
|
||||
convert -geometry "$resolution"x $file $output
|
||||
}
|
||||
|
||||
produce_webp () {
|
||||
name=$(file_name)
|
||||
output="$output_dir/$name.webp"
|
||||
cwebp $file -quiet -o $output
|
||||
}
|
||||
|
||||
progress() {
|
||||
local w=80 p=$1; shift
|
||||
# create a string of spaces, then change them to dots
|
||||
printf -v dots "%*s" "$(( $p*$w/100 ))" ""; dots=${dots// /.};
|
||||
# print those dots on a fixed-width space plus the percentage etc.
|
||||
printf "\r\e[K|%-*s| %3d %% %s" "$w" "$dots" "$p" "$*";
|
||||
}
|
||||
|
||||
|
||||
echo "Generating responsive versions of the pictures…"
|
||||
|
||||
if ! command -v convert &> /dev/null
|
||||
then
|
||||
echo "$(tput setaf 1)ERROR: The convert command could not be found. You need to install ImageMagick.$(tput sgr 0)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
nb_files=$( shopt -s nullglob ; set -- $output_dir/* ; echo $#)
|
||||
|
||||
tasks=$((${#resolutions[@]}*$nb_files))
|
||||
i=1
|
||||
for file in $output_dir/*
|
||||
do
|
||||
if [[ -f $file ]]; then
|
||||
for resolution in "${resolutions[@]}"; do
|
||||
convert_image $resolution
|
||||
progress $(($i*100/$tasks)) still working...
|
||||
i=$((i+1))
|
||||
done
|
||||
fi
|
||||
done
|
||||
echo -e "\nDone!"
|
||||
|
||||
echo "Generating optimized versions of the pictures…"
|
||||
|
||||
if ! command -v cwebp &> /dev/null
|
||||
then
|
||||
echo "$(tput setaf 1)ERROR: The cwebp command could not be found. You need to install webp.$(tput sgr 0)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
nb_files=$( shopt -s nullglob ; set -- $output_dir/* ; echo $#)
|
||||
i=1
|
||||
for file in $output_dir/*
|
||||
do
|
||||
if [[ -f $file ]]; then
|
||||
produce_webp
|
||||
progress $(($i*100/$nb_files)) still working...
|
||||
i=$((i+1))
|
||||
fi
|
||||
done
|
||||
echo -e "\nDone!"
|
@ -1,6 +1,22 @@
|
||||
<template>
|
||||
<footer class="footer" ref="footer">
|
||||
<img :src="`/img/pics/footer_${random}.jpg`" alt="" />
|
||||
<picture>
|
||||
<source
|
||||
:srcset="`/img/pics/footer_${random}-1024w.webp 1x, /img/pics/footer_${random}-1920w.webp 2x`"
|
||||
type="image/webp"
|
||||
/>
|
||||
<source
|
||||
:srcset="`/img/pics/footer_${random}-1024w.jpg 1x, /img/pics/footer_${random}-1920w.jpg 2x`"
|
||||
type="image/jpeg"
|
||||
/>
|
||||
<img
|
||||
:src="`/img/pics/footer_${random}-1024w.jpg`"
|
||||
alt=""
|
||||
width="5234"
|
||||
height="2189"
|
||||
loading="lazy"
|
||||
/>
|
||||
</picture>
|
||||
<ul>
|
||||
<li>
|
||||
<b-select
|
||||
|
10
js/src/utils/support.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export function supportsWebPFormat(): boolean {
|
||||
const elem = document.createElement("canvas");
|
||||
|
||||
if (elem.getContext && elem.getContext("2d")) {
|
||||
// was able or not to get WebP representation
|
||||
return elem.toDataURL("image/webp").indexOf("data:image/webp") === 0;
|
||||
}
|
||||
// very old browser like IE 8, canvas not supported
|
||||
return false;
|
||||
}
|
@ -101,7 +101,7 @@
|
||||
>
|
||||
<div class="columns is-vertical is-centered">
|
||||
<div class="column is-three-quarters">
|
||||
<div class="img-container" />
|
||||
<div class="img-container" :class="{ webp: supportsWebPFormat }" />
|
||||
<div class="content has-text-centered">
|
||||
<p>
|
||||
{{ $t("You didn't create or join any event yet.") }}
|
||||
@ -129,6 +129,7 @@
|
||||
import { Component, Vue } from "vue-property-decorator";
|
||||
import { ParticipantRole } from "@/types/enums";
|
||||
import RouteName from "@/router/name";
|
||||
import { supportsWebPFormat } from "@/utils/support";
|
||||
import { IParticipant, Participant } from "../../types/participant.model";
|
||||
import {
|
||||
LOGGED_USER_PARTICIPATIONS,
|
||||
@ -211,6 +212,8 @@ export default class MyEvents extends Vue {
|
||||
|
||||
RouteName = RouteName;
|
||||
|
||||
supportsWebPFormat = supportsWebPFormat;
|
||||
|
||||
static monthlyParticipations(
|
||||
participations: IParticipant[],
|
||||
revertSort = false
|
||||
@ -355,7 +358,21 @@ section {
|
||||
|
||||
.not-found {
|
||||
.img-container {
|
||||
background-image: url("/img/pics/2020-10-06-mobilizon-illustration-B_creation-evenement.jpg");
|
||||
background-image: url("/img/pics/event_creation-480w.jpg");
|
||||
@media (min-resolution: 2dppx) {
|
||||
& {
|
||||
background-image: url("/img/pics/event_creation-1024w.jpg");
|
||||
}
|
||||
}
|
||||
|
||||
&.webp {
|
||||
background-image: url("/img/pics/event_creation-480w.webp");
|
||||
@media (min-resolution: 2dppx) {
|
||||
& {
|
||||
background-image: url("/img/pics/event_creation-1024w.webp");
|
||||
}
|
||||
}
|
||||
}
|
||||
max-width: 450px;
|
||||
height: 300px;
|
||||
box-shadow: 0 0 8px 8px white inset;
|
||||
|
@ -46,7 +46,7 @@
|
||||
>
|
||||
<div class="columns is-vertical is-centered">
|
||||
<div class="column is-three-quarters">
|
||||
<div class="img-container" />
|
||||
<div class="img-container" :class="{ webp: supportsWebPFormat }" />
|
||||
<div class="content has-text-centered">
|
||||
<p>
|
||||
{{ $t("You are not part of any group.") }}
|
||||
@ -81,6 +81,7 @@ import { IGroup, usernameWithDomain } from "@/types/actor";
|
||||
import { Route } from "vue-router";
|
||||
import { IMember } from "@/types/actor/member.model";
|
||||
import { MemberRole } from "@/types/enums";
|
||||
import { supportsWebPFormat } from "@/utils/support";
|
||||
import RouteName from "../../router/name";
|
||||
|
||||
@Component({
|
||||
@ -119,6 +120,8 @@ export default class MyGroups extends Vue {
|
||||
|
||||
limit = 10;
|
||||
|
||||
supportsWebPFormat = supportsWebPFormat;
|
||||
|
||||
acceptInvitation(member: IMember): Promise<Route> {
|
||||
return this.$router.push({
|
||||
name: RouteName.GROUP,
|
||||
@ -199,7 +202,22 @@ section {
|
||||
|
||||
.not-found {
|
||||
.img-container {
|
||||
background-image: url("/img/pics/2020-10-06-mobilizon-illustration-C_groupe.jpg");
|
||||
background-image: url("/img/pics/group-480w.jpg");
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
& {
|
||||
background-image: url("/img/pics/group-1024w.jpg");
|
||||
}
|
||||
}
|
||||
&.webp {
|
||||
background-image: url("/img/pics/group-480w.webp");
|
||||
@media (min-resolution: 2dppx) {
|
||||
& {
|
||||
background-image: url("/img/pics/group-1024w.webp");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
max-width: 450px;
|
||||
height: 300px;
|
||||
box-shadow: 0 0 8px 8px white inset;
|
||||
|
@ -2,6 +2,7 @@
|
||||
<div id="homepage">
|
||||
<section
|
||||
class="hero"
|
||||
:class="{ webp: supportsWebPFormat }"
|
||||
v-if="config && (!currentUser.id || !currentActor.id)"
|
||||
>
|
||||
<div class="hero-body">
|
||||
@ -72,10 +73,59 @@
|
||||
</div>
|
||||
<div id="picture" v-if="config && (!currentUser.id || !currentActor.id)">
|
||||
<div class="picture-container">
|
||||
<img
|
||||
src="/img/pics/2020-10-06-mobilizon-illustration-A_homepage.jpg"
|
||||
alt=""
|
||||
/>
|
||||
<picture>
|
||||
<source
|
||||
media="(max-width: 799px)"
|
||||
srcset="/img/pics/homepage-480w.webp"
|
||||
type="image/webp"
|
||||
/>
|
||||
<source
|
||||
media="(max-width: 799px)"
|
||||
srcset="/img/pics/homepage-480w.jpg"
|
||||
type="image/jpeg"
|
||||
/>
|
||||
|
||||
<source
|
||||
media="(max-width: 1024px)"
|
||||
srcset="/img/pics/homepage-1024w.webp"
|
||||
type="image/webp"
|
||||
/>
|
||||
<source
|
||||
media="(max-width: 1024px)"
|
||||
srcset="/img/pics/homepage-1024w.jpg"
|
||||
type="image/jpeg"
|
||||
/>
|
||||
|
||||
<source
|
||||
media="(max-width: 1920px)"
|
||||
srcset="/img/pics/homepage-1920w.webp"
|
||||
type="image/webp"
|
||||
/>
|
||||
<source
|
||||
media="(max-width: 1920px)"
|
||||
srcset="/img/pics/homepage-1920w.jpg"
|
||||
type="image/jpeg"
|
||||
/>
|
||||
|
||||
<source
|
||||
media="(min-width: 1921px)"
|
||||
srcset="/img/pics/homepage.webp"
|
||||
type="image/webp"
|
||||
/>
|
||||
<source
|
||||
media="(min-width: 1921px)"
|
||||
srcset="/img/pics/homepage.jpg"
|
||||
type="image/jpeg"
|
||||
/>
|
||||
|
||||
<img
|
||||
src="/img/pics/homepage-1024w.jpg"
|
||||
width="3840"
|
||||
height="2719"
|
||||
alt=""
|
||||
loading="lazy"
|
||||
/>
|
||||
</picture>
|
||||
</div>
|
||||
<div class="container section">
|
||||
<div class="columns">
|
||||
@ -221,6 +271,7 @@
|
||||
import { Component, Vue, Watch } from "vue-property-decorator";
|
||||
import { ParticipantRole } from "@/types/enums";
|
||||
import { Paginate } from "@/types/paginate";
|
||||
import { supportsWebPFormat } from "@/utils/support";
|
||||
import { IParticipant, Participant } from "../types/participant.model";
|
||||
import { FETCH_EVENTS } from "../graphql/event";
|
||||
import EventListCard from "../components/Event/EventListCard.vue";
|
||||
@ -296,7 +347,10 @@ import Subtitle from "../components/Utils/Subtitle.vue";
|
||||
},
|
||||
})
|
||||
export default class Home extends Vue {
|
||||
events!: Paginate<IEvent>;
|
||||
events: Paginate<IEvent> = {
|
||||
elements: [],
|
||||
total: 0,
|
||||
};
|
||||
|
||||
locations = [];
|
||||
|
||||
@ -316,6 +370,8 @@ export default class Home extends Vue {
|
||||
|
||||
currentUserParticipations: IParticipant[] = [];
|
||||
|
||||
supportsWebPFormat = supportsWebPFormat;
|
||||
|
||||
// get displayed_name() {
|
||||
// return this.loggedPerson && this.loggedPerson.name === null
|
||||
// ? this.loggedPerson.preferredUsername
|
||||
@ -531,7 +587,11 @@ section.hero {
|
||||
height: 100%;
|
||||
opacity: 0.3;
|
||||
z-index: -1;
|
||||
background: url("/img/pics/homepage_background.png");
|
||||
background: url("/img/pics/homepage_background-1024w.png");
|
||||
background-size: cover;
|
||||
}
|
||||
&.webp::before {
|
||||
background-image: url("/img/pics/homepage_background-1024w.webp");
|
||||
}
|
||||
|
||||
& > .hero-body {
|
||||
|
@ -2,10 +2,24 @@
|
||||
<section class="section container has-text-centered not-found">
|
||||
<div class="columns is-vertical is-centered">
|
||||
<div class="column is-half">
|
||||
<img
|
||||
src="/img/pics/2020-10-06-mobilizon-illustration-E_realisation.jpg"
|
||||
alt=""
|
||||
/>
|
||||
<picture>
|
||||
<source
|
||||
srcset="/img/pics/error-480w.webp 1x, /img/pics/error-1024w.webp 2x"
|
||||
type="image/webp"
|
||||
/>
|
||||
<source
|
||||
srcset="/img/pics/error-480w.jpg 1x, /img/pics/error-1024w.jpg 2x"
|
||||
type="image/jpeg"
|
||||
/>
|
||||
|
||||
<img
|
||||
:src="`/img/pics/error-480w.jpg`"
|
||||
alt=""
|
||||
width="2616"
|
||||
height="1698"
|
||||
loading="lazy"
|
||||
/>
|
||||
</picture>
|
||||
<h1 class="title">
|
||||
{{ $t("The page you're looking for doesn't exist.") }}
|
||||
</h1>
|
||||
|
24
js/yarn.lock
@ -2701,7 +2701,7 @@ async@2.6.1:
|
||||
dependencies:
|
||||
lodash "^4.17.10"
|
||||
|
||||
async@^2.6.2:
|
||||
async@^2.6.1, async@^2.6.2:
|
||||
version "2.6.3"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff"
|
||||
integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==
|
||||
@ -10489,6 +10489,11 @@ resolve-dir@^1.0.0, resolve-dir@^1.0.1:
|
||||
expand-tilde "^2.0.0"
|
||||
global-modules "^1.0.0"
|
||||
|
||||
resolve-from@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-2.0.0.tgz#9480ab20e94ffa1d9e80a804c7ea147611966b57"
|
||||
integrity sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=
|
||||
|
||||
resolve-from@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748"
|
||||
@ -10499,6 +10504,13 @@ resolve-from@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
|
||||
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
|
||||
|
||||
resolve-pkg@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve-pkg/-/resolve-pkg-1.0.0.tgz#e19a15e78aca2e124461dc92b2e3943ef93494d9"
|
||||
integrity sha1-4ZoV54rKLhJEYdySsuOUPvk0lNk=
|
||||
dependencies:
|
||||
resolve-from "^2.0.0"
|
||||
|
||||
resolve-url@^0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
|
||||
@ -10739,6 +10751,16 @@ schema-utils@^3.0.0:
|
||||
ajv "^6.12.5"
|
||||
ajv-keywords "^3.5.2"
|
||||
|
||||
scripty@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/scripty/-/scripty-2.0.0.tgz#25761bb2e237a7563f705d87357db07791d38459"
|
||||
integrity sha512-vbd4FPeuNwYNGtRtYa1wDZLPCx5PpW6VrldCEiBGqPz7Je1xZOgNvVPD2axymvqNghBIRiXxAU+JwYrOzvuLJg==
|
||||
dependencies:
|
||||
async "^2.6.1"
|
||||
glob "^7.0.3"
|
||||
lodash "^4.17.11"
|
||||
resolve-pkg "^1.0.0"
|
||||
|
||||
select-hose@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
|
||||
|