active_storage-blurhash 0.1.0 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +28 -4
- data/app/helpers/blurhash_image_helper.rb~ +37 -0
- data/lib/active_storage/blurhash/analyzer/image_magick.rb~ +8 -0
- data/lib/active_storage/blurhash/analyzer/vips.rb~ +8 -0
- data/lib/active_storage/blurhash/analyzer.rb~ +9 -0
- data/lib/active_storage/blurhash/analyzing.rb~ +9 -0
- data/lib/active_storage/blurhash/engine.rb~ +6 -0
- data/lib/active_storage/blurhash/thumbnail/image_magick.rb~ +19 -0
- data/lib/active_storage/blurhash/thumbnail/vips.rb~ +19 -0
- data/lib/active_storage/blurhash/version.rb +1 -1
- data/lib/active_storage/blurhash.rb~ +8 -0
- data/lib/generators/active_storage/blurhash/install/USAGE~ +8 -0
- data/lib/generators/active_storage/blurhash/install/install_generator.rb +18 -2
- data/lib/generators/active_storage/blurhash/install/install_generator.rb~ +3 -0
- data/lib/generators/active_storage/blurhash/install/templates/javascript/index.js~ +79 -0
- data/lib/tasks/active_storage/blurhash_tasks.rake~ +4 -0
- metadata +17 -4
- /data/lib/{active_storage/blurhash.rb → active_storage-blurhash.rb} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fba342284d825997b007b2c6211737e52c9074eca393c1f9566698c3a37bf179
|
4
|
+
data.tar.gz: 33f2ba27eaf2b54b21e4510414014deb2f2f864aef17524788d70db61e355420
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '08daf2c1486f453fda2275c1c86038d93925aab11154a97136489c2fd48867e21b71d83ff40946d999b89694ac50ed95b14c8358c3230231d511a7fb2509c879'
|
7
|
+
data.tar.gz: 6c7f1be0ef09d472455e2afd570d42f62b65535d62c862323941d45142cce5cc0843c2214e3150dc5259bada9bf3e2d8f302668fdced8d58ae915fa28652c1ae
|
data/README.md
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
# ActiveStorage::Blurhash
|
2
2
|
|
3
|
-
A [blurhash](https://blurha.sh/) integration for images stored in ActiveStorage.
|
3
|
+
A [blurhash](https://blurha.sh/) integration for images stored in ActiveStorage.
|
4
4
|
|
5
5
|
## Motivation
|
6
|
+
|
6
7
|
Elimination of layout shift and speeding up First/Largest Contentful Paint are among the primary goals for improving Core Web Vitals. For both scenarios, lazy loading images while displaying a temporary Blurhash before swapping it out for the actual image is a great way to enhance the loading experience and perceived performance.
|
7
8
|
|
8
9
|
## Usage
|
@@ -25,26 +26,35 @@ This will create a wrapper `<div>` containing a `<canvas>` that is going to be p
|
|
25
26
|
|
26
27
|
Make sure to run the install generator and backfill any existing attachments (see below). New attachments should be automatically analyzed to include the blurhash metadata.
|
27
28
|
|
28
|
-
|
29
29
|
## Installation
|
30
|
-
|
30
|
+
|
31
|
+
Add the library to your application's Gemfile:
|
31
32
|
|
32
33
|
```ruby
|
33
34
|
gem "active_storage-blurhash"
|
34
35
|
```
|
35
36
|
|
37
|
+
You will also need to add the "image_processing" gem to your Gemfile:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
gem "image_processing"
|
41
|
+
```
|
42
|
+
|
36
43
|
And then execute:
|
44
|
+
|
37
45
|
```bash
|
38
46
|
$ bundle
|
39
47
|
```
|
40
48
|
|
41
|
-
|
42
49
|
Install the JavaScript packages and blurhash snippet:
|
43
50
|
|
44
51
|
```bash
|
45
52
|
$ bin/rails g active_storage:blurhash:install
|
46
53
|
```
|
47
54
|
|
55
|
+
> [!IMPORTANT]
|
56
|
+
> Consider that, to make everything work, you need [libvips](https://github.com/janko/image_processing/blob/master/doc/vips.md) or [MiniMagick](https://github.com/janko/image_processing/blob/master/doc/minimagick.md) installed on your system.
|
57
|
+
|
48
58
|
## Backfilling
|
49
59
|
|
50
60
|
Chances are you already have a large assortment of ActiveStorage attachments. In this case, we can help you out with a backfill rake task that re-analyzes all existing image blobs:
|
@@ -62,4 +72,18 @@ $ BATCH_SIZE=50 bin/rails active_storage_blurhash:backfill
|
|
62
72
|
Note that for each blob an `AnalyzeJob` will be appended to your job processor queue.
|
63
73
|
|
64
74
|
## License
|
75
|
+
|
65
76
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
77
|
+
|
78
|
+
## Other gems & packages
|
79
|
+
|
80
|
+
- [`avo`](https://github.com/avo-hq/avo) - Build Content management systems with Ruby on Rails
|
81
|
+
- [`class_variants`](https://github.com/avo-hq/class_variants) - Easily configure styles and apply them as classes. Very useful when you're implementing Tailwind CSS components and call them with different states.
|
82
|
+
- [`prop_initializer`](https://github.com/avo-hq/prop_initializer) - A flexible tool for defining properties on Ruby classes.
|
83
|
+
- [`stimulus-confetti`](https://github.com/avo-hq/stimulus-confetti) - The easiest way to add confetti to your StimulusJS app
|
84
|
+
|
85
|
+
## Try Avo ⭐️
|
86
|
+
|
87
|
+
If you enjoyed this gem try out [Avo](https://github.com/avo-hq/avo). It helps developers build Internal Tools, Admin Panels, CMSes, CRMs, and any other type of Business Apps 10x faster on top of Ruby on Rails.
|
88
|
+
|
89
|
+
[![](https://github.com/avo-hq/avo/raw/main/public/avo-assets/logo-on-white.png)](https://github.com/avo-hq/avo)
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BlurhashImageHelper
|
4
|
+
def blurhash_image_tag(source, options = {})
|
5
|
+
case source
|
6
|
+
when String
|
7
|
+
# if a URL is passed, we have to manually re-hydrate the blob from it
|
8
|
+
path_parameters = Rails.application.routes.recognize_path(source)
|
9
|
+
blob = ActiveStorage::Blob.find_signed!(path_parameters[:signed_blob_id] || path_parameters[:signed_id])
|
10
|
+
when ActiveStorage::Blob
|
11
|
+
blob = source
|
12
|
+
when ActiveStorage::Attached::One
|
13
|
+
blob = source.blob
|
14
|
+
when ActiveStorage::VariantWithRecord
|
15
|
+
blob = source.blob
|
16
|
+
size = source.variation.transformations[:resize]
|
17
|
+
end
|
18
|
+
|
19
|
+
blurhash = blob.metadata["blurhash"]
|
20
|
+
size ||= "#{blob.metadata["width"]}x#{blob.metadata["height"]}"
|
21
|
+
|
22
|
+
if !!blurhash
|
23
|
+
options[:loading] = "lazy"
|
24
|
+
options[:size] = size
|
25
|
+
|
26
|
+
wrapper_class = options.delete(:wrapper_class)
|
27
|
+
canvas_class = options.delete(:canvas_class)
|
28
|
+
tag.div class: wrapper_class, data: {blurhash: blurhash}, style: "position: relative" do
|
29
|
+
image_tag(source, options) + tag.canvas(style: "position: absolute; inset: 0; transition-property: opacity; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms;", class: canvas_class)
|
30
|
+
end
|
31
|
+
else
|
32
|
+
image_tag(source, options)
|
33
|
+
end
|
34
|
+
rescue ActionController::RoutingError
|
35
|
+
image_tag(source, options)
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ActiveStorage
|
2
|
+
module Blurhash
|
3
|
+
module Thumbnail
|
4
|
+
class ImageMagick
|
5
|
+
delegate_missing_to :@thumbnail
|
6
|
+
|
7
|
+
def initialize(image)
|
8
|
+
@thumbnail = MiniMagick::Image.new(
|
9
|
+
ImageProcessing::MiniMagick.source(image.path).resize_to_limit(200, 200).call
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
13
|
+
def pixels
|
14
|
+
@thumbnail.get_pixels.flatten
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ActiveStorage
|
2
|
+
module Blurhash
|
3
|
+
module Thumbnail
|
4
|
+
class Vips
|
5
|
+
delegate_missing_to :@thumbnail
|
6
|
+
|
7
|
+
def initialize(image)
|
8
|
+
@thumbnail = MiniMagick::Image.open(
|
9
|
+
ImageProcessing::MiniMagick.source(image.path).resize_to_limit(200, 200).call.path
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
13
|
+
def pixels
|
14
|
+
@thumbnail.get_pixels.flatten
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -2,7 +2,7 @@ class ActiveStorage::Blurhash::InstallGenerator < Rails::Generators::Base
|
|
2
2
|
source_root File.expand_path("templates", __dir__)
|
3
3
|
|
4
4
|
def install_javascript_deps
|
5
|
-
if
|
5
|
+
if importmap_present?
|
6
6
|
say "Pinning blurhash"
|
7
7
|
run "bin/importmap pin blurhash"
|
8
8
|
else
|
@@ -15,7 +15,23 @@ class ActiveStorage::Blurhash::InstallGenerator < Rails::Generators::Base
|
|
15
15
|
directory "javascript", "app/javascript/blurhash"
|
16
16
|
end
|
17
17
|
|
18
|
+
def pin_blurhash_javascript
|
19
|
+
if importmap_present?
|
20
|
+
append_to_file "config/importmap.rb", 'pin "active_storage_blurhash", to: "blurhash/index.js"' + "\n"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
18
24
|
def append_to_main_javascript_entrypoint
|
19
|
-
|
25
|
+
if importmap_present?
|
26
|
+
append_to_file "app/javascript/application.js", "import \"active_storage_blurhash\";\n"
|
27
|
+
else
|
28
|
+
append_to_file "app/javascript/application.js", "import \"./blurhash\";\n"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def importmap_present?
|
35
|
+
File.exist? Rails.root.join("config", "importmap.rb")
|
20
36
|
end
|
21
37
|
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
import { decode } from "blurhash";
|
2
|
+
|
3
|
+
window.ActiveStorageBlurhash = {
|
4
|
+
observer: new MutationObserver((mutationList, observer) => {
|
5
|
+
mutationList.forEach(({ type, target, addedNodes, attributeName }) => {
|
6
|
+
switch (type) {
|
7
|
+
case "childList":
|
8
|
+
if (addedNodes.length === 0) return;
|
9
|
+
|
10
|
+
Array.from(addedNodes)
|
11
|
+
.filter((node) => {
|
12
|
+
try {
|
13
|
+
return "blurhash" in node.dataset;
|
14
|
+
} catch (e) {
|
15
|
+
return false;
|
16
|
+
}
|
17
|
+
})
|
18
|
+
.forEach((node) => {
|
19
|
+
window.ActiveStorageBlurhash.renderAndLoad(node);
|
20
|
+
});
|
21
|
+
break;
|
22
|
+
case "attributes":
|
23
|
+
window.ActiveStorageBlurhash.renderAndLoad(target);
|
24
|
+
break;
|
25
|
+
}
|
26
|
+
});
|
27
|
+
}),
|
28
|
+
renderAndLoad(wrapper) {
|
29
|
+
const image = wrapper.querySelector("img");
|
30
|
+
|
31
|
+
// the image might already be completely loaded. In this case we need to do nothing
|
32
|
+
if (image.complete) return;
|
33
|
+
|
34
|
+
// if the image comes in with empty dimensions, we can't assign canvas data
|
35
|
+
if (image.width === 0 || image.height === 0) return;
|
36
|
+
|
37
|
+
const width = image.width;
|
38
|
+
const height = image.height;
|
39
|
+
|
40
|
+
const canvas = wrapper.querySelector("canvas");
|
41
|
+
|
42
|
+
canvas.width = width;
|
43
|
+
canvas.height = height;
|
44
|
+
|
45
|
+
const pixels = decode(wrapper.dataset.blurhash, width, height);
|
46
|
+
const ctx = canvas.getContext("2d");
|
47
|
+
const imageData = ctx.createImageData(width, height);
|
48
|
+
imageData.data.set(pixels);
|
49
|
+
ctx.putImageData(imageData, 0, 0);
|
50
|
+
|
51
|
+
const swap = () => {
|
52
|
+
canvas.style.opacity = "0";
|
53
|
+
};
|
54
|
+
|
55
|
+
if (image.complete) {
|
56
|
+
// the image might already have been loaded
|
57
|
+
swap();
|
58
|
+
} else {
|
59
|
+
// else we need to wait for it to load
|
60
|
+
image.onload = swap;
|
61
|
+
}
|
62
|
+
},
|
63
|
+
};
|
64
|
+
|
65
|
+
document.addEventListener("turbo:load", () => {
|
66
|
+
document.querySelectorAll("div[data-blurhash]").forEach((wrapper) => {
|
67
|
+
window.ActiveStorageBlurhash.renderAndLoad(wrapper);
|
68
|
+
});
|
69
|
+
|
70
|
+
window.ActiveStorageBlurhash.observer.disconnect();
|
71
|
+
window.ActiveStorageBlurhash.observer.observe(document.body, {
|
72
|
+
subtree: true,
|
73
|
+
childList: true,
|
74
|
+
attributes: true,
|
75
|
+
attributeFilter: ["data-blurhash"],
|
76
|
+
});
|
77
|
+
});
|
78
|
+
|
79
|
+
document.addEventListner("turbo:before-cache", () => {});
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_storage-blurhash
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Julian Rubisch
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-12-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activestorage
|
@@ -148,19 +148,32 @@ files:
|
|
148
148
|
- Rakefile
|
149
149
|
- app/assets/config/active_storage_blurhash_manifest.js
|
150
150
|
- app/helpers/blurhash_image_helper.rb
|
151
|
+
- app/helpers/blurhash_image_helper.rb~
|
151
152
|
- config/routes.rb
|
152
|
-
- lib/active_storage
|
153
|
+
- lib/active_storage-blurhash.rb
|
154
|
+
- lib/active_storage/blurhash.rb~
|
155
|
+
- lib/active_storage/blurhash/analyzer.rb~
|
153
156
|
- lib/active_storage/blurhash/analyzer/image_magick.rb
|
157
|
+
- lib/active_storage/blurhash/analyzer/image_magick.rb~
|
154
158
|
- lib/active_storage/blurhash/analyzer/vips.rb
|
159
|
+
- lib/active_storage/blurhash/analyzer/vips.rb~
|
155
160
|
- lib/active_storage/blurhash/analyzing.rb
|
161
|
+
- lib/active_storage/blurhash/analyzing.rb~
|
156
162
|
- lib/active_storage/blurhash/engine.rb
|
163
|
+
- lib/active_storage/blurhash/engine.rb~
|
157
164
|
- lib/active_storage/blurhash/thumbnail/image_magick.rb
|
165
|
+
- lib/active_storage/blurhash/thumbnail/image_magick.rb~
|
158
166
|
- lib/active_storage/blurhash/thumbnail/vips.rb
|
167
|
+
- lib/active_storage/blurhash/thumbnail/vips.rb~
|
159
168
|
- lib/active_storage/blurhash/version.rb
|
160
169
|
- lib/generators/active_storage/blurhash/install/USAGE
|
170
|
+
- lib/generators/active_storage/blurhash/install/USAGE~
|
161
171
|
- lib/generators/active_storage/blurhash/install/install_generator.rb
|
172
|
+
- lib/generators/active_storage/blurhash/install/install_generator.rb~
|
162
173
|
- lib/generators/active_storage/blurhash/install/templates/javascript/index.js
|
174
|
+
- lib/generators/active_storage/blurhash/install/templates/javascript/index.js~
|
163
175
|
- lib/tasks/active_storage/blurhash_tasks.rake
|
176
|
+
- lib/tasks/active_storage/blurhash_tasks.rake~
|
164
177
|
homepage: https://github.com/avo-hq/active_storage-blurhash
|
165
178
|
licenses:
|
166
179
|
- MIT
|
@@ -182,7 +195,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
182
195
|
- !ruby/object:Gem::Version
|
183
196
|
version: '0'
|
184
197
|
requirements: []
|
185
|
-
rubygems_version: 3.
|
198
|
+
rubygems_version: 3.5.19
|
186
199
|
signing_key:
|
187
200
|
specification_version: 4
|
188
201
|
summary: Use blurhashes to lazy load ActiveStorage images
|
File without changes
|