active_storage-blurhash 0.1.1 → 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: 9687ba74613e9674476132209feb5a9d3c69f03b2afe424fca8c36d57cb37fa4
4
- data.tar.gz: 8a0aa6d0e954c745ec410ac0826444170b9d336b00d2fba503ab16422ff7471f
3
+ metadata.gz: fba342284d825997b007b2c6211737e52c9074eca393c1f9566698c3a37bf179
4
+ data.tar.gz: 33f2ba27eaf2b54b21e4510414014deb2f2f864aef17524788d70db61e355420
5
5
  SHA512:
6
- metadata.gz: d7972f70cc32ebe1ac99e9d812c953f65645e0450deb4b7e55b5afd7521ad84ca96f126cf7cf7173c894517e9c254b0686ce6c3a92c858cfed8a38be0af71b39
7
- data.tar.gz: a7811a663e82d96b0342316f468a4f2ee1c7eff85d6701d28f64a21e574c3c0fe3e32c71838ab8e3bff968dbd0b7f09ff7d1d68f87ea1f921634511bcfc53691
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,16 +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).
66
77
 
67
78
  ## Other gems & packages
68
79
 
69
- - [`avo`](https://github.com/avo-hq/avo) - Build Content management systems with Ruby on Rails
70
- - [`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.
71
- - [`stimulus-confetti`](https://github.com/avo-hq/stimulus-confetti) - The easiest way to add confetti to your StimulusJS app
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
72
84
 
73
85
  ## Try Avo ⭐️
74
86
 
75
- If you enjoyed this gem try out Avo. It helps developers build internal tools, admins, CMSes, CRMs, and any other type of business apps 10x faster on top of Ruby on Rails.
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.
76
88
 
77
- [![](https://github.com/avo-hq/avo/raw/main/public/avo-assets/logo-on-white.png)](https://github.com/avo-hq/avo)
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.1"
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.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-08-22 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
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