active_storage-blurhash 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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)
|
@@ -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
|