Skip to content

Commit

Permalink
feat: add ability to pass in custom widths (#55)
Browse files Browse the repository at this point in the history
  • Loading branch information
sherwinski authored Nov 8, 2019
1 parent eb5d4c8 commit 6b79413
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 15 deletions.
25 changes: 23 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ https://your-subdomain.imgix.net/images/demo.png?w=7400&s=a5dd7dda1dbac613f0475f
https://your-subdomain.imgix.net/images/demo.png?w=8192&s=9fbd257c53e770e345ce3412b64a3452 8192w
```

**Fixed image rendering**
### Fixed image rendering

In cases where enough information is provided about an image's dimensions, `to_srcset` will instead build a `srcset` that will allow for an image to be served at different resolutions. The parameters taken into consideration when determining if an image is fixed-width are `w`, `h`, and `ar`. By invoking `to_srcset` with either a width **or** the height and aspect ratio (along with `fit=crop`, typically) provided, a different `srcset` will be generated for a fixed-size image instead.

Expand All @@ -91,7 +91,28 @@ https://your-subdomain.imgix.net/images/demo.png?h=800&ar=3%3A2&fit=crop&dpr=5&s

For more information to better understand `srcset`, we highly recommend [Eric Portis' "Srcset and sizes" article](https://ericportis.com/posts/2014/srcset-sizes/) which goes into depth about the subject.

**Width Tolerance**
### Custom Widths

In situations where specific widths are desired when generating `srcset` pairs, a user can specify them by passing an array of integers to the `widths` keyword argument.

```rb
@client ||= Imgix::Client.new(host: 'testing.imgix.net', include_library_param: false)
.path('image.jpg')
.to_srcset(widths: [100, 500, 1000, 1800])
```

Will generate the following `srcset` of width pairs:

```html
https://testing.imgix.net/image.jpg?w=100 100w,
https://testing.imgix.net/image.jpg?w=500 500w,
https://testing.imgix.net/image.jpg?w=1000 1000w,
https://testing.imgix.net/image.jpg?w=1800 1800w
```

Please note that in situations where a `srcset` is being rendered as a [fixed image](#fixed-image-rendering), any custom `widths` passed in will be ignored. Additionally, if both `widths` and a `width_tolerance` are passed to the `to_srcset` method, the custom widths list will take precedence.

### Width Tolerance

The `srcset` width tolerance dictates the maximum tolerated size difference between an image's downloaded size and its rendered size. For example: setting this value to 0.1 means that an image will not render more than 10% larger or smaller than its native size. In practice, the image URLs generated for a width-based srcset attribute will grow by twice this rate. A lower tolerance means images will render closer to their native size (thereby reducing rendering artifacts), but a large srcset list will be generated and consequently users may experience lower rates of cache-hit for pre-rendered images on your site.

Expand Down
29 changes: 22 additions & 7 deletions lib/imgix/path.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def method_missing(method, *args, &block)
end
end

def to_srcset(width_tolerance: DEFAULT_WIDTH_TOLERANCE, **params)
def to_srcset(widths: [], width_tolerance: DEFAULT_WIDTH_TOLERANCE, **params)
prev_options = @options.dup
@options.merge!(params)

Expand All @@ -95,7 +95,7 @@ def to_srcset(width_tolerance: DEFAULT_WIDTH_TOLERANCE, **params)
if ((width) || (height && aspect_ratio))
srcset = build_dpr_srcset(@options)
else
srcset = build_srcset_pairs(width_tolerance: width_tolerance, params: @options)
srcset = build_srcset_pairs(widths: widths, width_tolerance: width_tolerance, params: @options)
end

@options = prev_options
Expand Down Expand Up @@ -128,16 +128,19 @@ def has_query?
query.length > 0
end

def build_srcset_pairs(width_tolerance:, params:)
def build_srcset_pairs(widths:, width_tolerance:, params:)
srcset = ''

unless width_tolerance == DEFAULT_WIDTH_TOLERANCE
widths = TARGET_WIDTHS.call(width_tolerance)
if !widths.empty?
validate_widths!(widths)
srcset_widths = widths
elsif width_tolerance != DEFAULT_WIDTH_TOLERANCE
srcset_widths = TARGET_WIDTHS.call(width_tolerance)
else
widths = @target_widths
srcset_widths = @target_widths
end

for width in widths do
for width in srcset_widths do
params['w'.to_sym] = width
srcset += "#{to_url(params)} #{width}w,\n"
end
Expand All @@ -156,5 +159,17 @@ def build_dpr_srcset(params)

srcset[0..-3]
end

def validate_widths!(widths)
unless widths.is_a? Array
raise ArgumentError, "The widths argument must be passed a valid array of integers"
else
positive_integers = widths.all? {|i| i.is_a?(Integer) and i > 0}
unless positive_integers
raise ArgumentError, "A custom widths array must only contain positive integer values"
end
end
end

end
end
90 changes: 84 additions & 6 deletions test/units/srcset_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ def test_no_parameters
expected_number_of_pairs = 31
assert_equal expected_number_of_pairs, srcset.split(',').length
end

def test_srcset_pair_values
resolutions = [100, 116, 134, 156, 182, 210, 244, 282,
328, 380, 442, 512, 594, 688, 798, 926,
Expand Down Expand Up @@ -67,7 +67,7 @@ def test_srcset_signs_urls
assert_equal expected_signature, generated_signature
}
end

private
def srcset
@client ||= Imgix::Client.new(host: 'testing.imgix.net', secure_url_token: 'MYT0KEN', include_library_param: false).path('image.jpg').to_srcset(w:100)
Expand Down Expand Up @@ -186,7 +186,7 @@ def test_srcset_signs_urls

signature_base = 'MYT0KEN' + '/image.jpg' + params;
expected_signature = Digest::MD5.hexdigest(signature_base)

assert_equal expected_signature, generated_signature
}
end
Expand Down Expand Up @@ -303,7 +303,7 @@ def test_srcset_signs_urls

signature_base = 'MYT0KEN' + '/image.jpg' + params;
expected_signature = Digest::MD5.hexdigest(signature_base)

assert_equal expected_signature, generated_signature
}
end
Expand Down Expand Up @@ -337,7 +337,6 @@ def test_srcset_within_bounds
# parse out the width descriptor as an integer
min = min.split(' ')[1].to_i
max = max[max.length - 1].split(' ')[1].to_i

assert_operator min, :>=, 100
assert_operator max, :<=, 8192
end
Expand Down Expand Up @@ -397,4 +396,83 @@ def srcset
@client ||= Imgix::Client.new(host: 'testing.imgix.net', secure_url_token: 'MYT0KEN', include_library_param: false).path('image.jpg').to_srcset(width_tolerance: 0.20)
end
end
end

class SrcsetCustomWidths < Imgix::Test
def test_srcset_generates_width_pairs
expected_number_of_pairs = 4
assert_equal expected_number_of_pairs, srcset.split(',').length
end

def test_srcset_pair_values
resolutions = [100, 500, 1000, 1800]
srclist = srcset.split(',').map { |srcset_split|
srcset_split.split(' ')[1].to_i
}

for i in 0..srclist.length - 1 do
assert_equal(srclist[i], resolutions[i])
end
end

def test_srcset_within_bounds
min, *max = srcset.split(',')

# parse out the width descriptor as an integer
min = min.split(' ')[1].to_i
max = max[max.length - 1].split(' ')[1].to_i

assert_operator min, :>=, @widths[0]
assert_operator max, :<=, @widths[-1]
end

def test_invalid_widths_input_emits_error
assert_raises(ArgumentError) {
Imgix::Client.new(host: 'testing.imgix.net')
.path('image.jpg')
.to_srcset(widths: 'abc')
}
end

def test_non_integer_array_emits_error
assert_raises(ArgumentError) {
Imgix::Client.new(host: 'testing.imgix.net')
.path('image.jpg')
.to_srcset(widths: [100, 200, false])
}
end

def test_negative_integer_array_emits_error
assert_raises(ArgumentError) {
Imgix::Client.new(host: 'testing.imgix.net')
.path('image.jpg')
.to_srcset(widths: [100, 200, -100])
}
end

def test_with_param_after
srcset = Imgix::Client.new(host: 'testing.imgix.net', secure_url_token: 'MYT0KEN', include_library_param: false)
.path('image.jpg')
.to_srcset(widths: [100, 200, 300], h:1000, fit:"clip")
assert_includes(srcset, "h=")
assert(not(srcset.include? "widths="))
end

def test_with_param_before
srcset = Imgix::Client.new(host: 'testing.imgix.net', secure_url_token: 'MYT0KEN', include_library_param: false)
.path('image.jpg')
.to_srcset(h:1000, fit:"clip", widths: [100, 200, 300])
assert_includes(srcset, "h=")
assert(not(srcset.include? "widths="))
end

private
def srcset
@widths = [100, 500, 1000, 1800]
@client ||= Imgix::Client.new(
host: 'testing.imgix.net',
include_library_param: false)
.path('image.jpg')
.to_srcset(widths: @widths)
end
end
end

0 comments on commit 6b79413

Please sign in to comment.