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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b2017d1770807244bbd6a7c5478f4ecb59a5f13ee5b7eb5a086c2f09856973f0
4
- data.tar.gz: 9f086097d1d55813b5c0edae2e221a9470af3260e442622ca294c05646fcf48e
3
+ metadata.gz: fba342284d825997b007b2c6211737e52c9074eca393c1f9566698c3a37bf179
4
+ data.tar.gz: 33f2ba27eaf2b54b21e4510414014deb2f2f864aef17524788d70db61e355420
5
5
  SHA512:
6
- metadata.gz: 0e47a24da41f183e833387d0d2d03eac0c22dfb032044a333e328be4f04d41c79d638966adc0af9672e30ec5bd464c08e44a595d4e9d45b322891d2f20723578
7
- data.tar.gz: 4700ccb58f8bc1e8e0b93e8f7207a09e3ebac00e0773fe8675fa2f88489fe2c4c2ff852c22a68b41670cb7db30559d1b056b3b19ef864cd11d7cfd33102e79d0
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
- Add this line to your application's Gemfile:
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,8 @@
1
+ module ActiveStorage
2
+ module Blurhash
3
+ module Analyzer
4
+ class ImageMagick
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module ActiveStorage
2
+ module Blurhash
3
+ module Analyzer
4
+ class Vips < ActiveStorage::Analyzer::ImageAnalyzer::Vips
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,9 @@
1
+ module ActiveStorage
2
+ module Blurhash
3
+ class Analyzer < ActiveStorage::Analyzer::ImageAnalyzer
4
+ def metadata
5
+ super
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module ActiveStorage
2
+ module Blurhash
3
+ module Analyzing
4
+ def metadata
5
+ super.merge({foo: :bar})
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ module ActiveStorage
2
+ module Blurhash
3
+ class Engine < ::Rails::Engine
4
+ end
5
+ end
6
+ 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
@@ -1,5 +1,5 @@
1
1
  module ActiveStorage
2
2
  module Blurhash
3
- VERSION = "0.1.0"
3
+ VERSION = "0.1.2"
4
4
  end
5
5
  end
@@ -0,0 +1,8 @@
1
+ require "active_storage/blurhash/version"
2
+ require "active_storage/blurhash/engine"
3
+
4
+ module ActiveStorage
5
+ module Blurhash
6
+ # Your code goes here...
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Explain the generator
3
+
4
+ Example:
5
+ bin/rails generate install Thing
6
+
7
+ This will create:
8
+ what/will/it/create
@@ -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 File.exist? Rails.root.join("config", "importmap.rb")
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
- append_to_file "app/javascript/application.js", "import \"./blurhash\";\n"
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,3 @@
1
+ class ActiveStorage::Blurhash::InstallGenerator < Rails::Generators::NamedBase
2
+ source_root File.expand_path("templates", __dir__)
3
+ 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", () => {});
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :active_storage_blurhash do
3
+ # # Task goes here
4
+ # end
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.0
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-08-21 00:00:00.000000000 Z
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/blurhash.rb
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.4.10
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