diff --git a/Gemfile b/Gemfile index f13ba99..1b8ff54 100644 --- a/Gemfile +++ b/Gemfile @@ -14,6 +14,4 @@ gemspec group :development, :test do gem 'byebug' - gem 'teaspoon-jasmine' - gem 'coffee-rails' end diff --git a/Gemfile.lock b/Gemfile.lock index 0133d76..789e29e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -48,16 +48,8 @@ GEM bump (0.5.2) byebug (5.0.0) columnize (= 0.9.0) - coffee-rails (4.1.0) - coffee-script (>= 2.2.0) - railties (>= 4.0.0, < 5.0) - coffee-script (2.4.1) - coffee-script-source - execjs - coffee-script-source (1.9.1.1) columnize (0.9.0) erubis (2.7.0) - execjs (2.5.2) globalid (0.3.6) activesupport (>= 4.1.0) i18n (0.7.0) @@ -106,10 +98,6 @@ GEM activesupport (>= 3.0) sprockets (>= 2.8, < 4.0) sqlite3 (1.3.10) - teaspoon (1.0.2) - railties (>= 3.2.5, < 5) - teaspoon-jasmine (2.2.0) - teaspoon (>= 1.0.0) thor (0.19.1) thread_safe (0.3.5) tzinfo (1.2.2) @@ -121,10 +109,8 @@ PLATFORMS DEPENDENCIES bump (~> 0.5.2) byebug - coffee-rails lazy_images-rails! sqlite3 - teaspoon-jasmine BUNDLED WITH 1.10.6 diff --git a/README.md b/README.md index ee05708..a7cb5f7 100644 --- a/README.md +++ b/README.md @@ -12,21 +12,11 @@ This problem can be solved in many ways, but the solution provided by this plugi ### How? -Instead of rendering a bare `` tag, the Rails [`image_tag`](http://api.rubyonrails.org/classes/ActionView/Helpers/AssetTagHelper.html#method-i-image_tag) helper renders an SVG placeholder with the relevant data for the image instead. This placeholder takes up the same space as it's constituent image. - -Once the DOM is ready, the page is scanned for placeholders and each one is then replaced by it's image. - -In case of non-js browsers, a noscript fallback is provided with the placeholder. +Instead of rendering a bare `` tag, the Rails [`image_tag`](http://api.rubyonrails.org/classes/ActionView/Helpers/AssetTagHelper.html#method-i-image_tag) helper renders an SVG placeholder behind the image while it is loading. This placeholder takes up the same space as it's constituent image. ## Usage -Include the supplied assets in the respective manifests, `application.js`: - -```javascript -//= require lazy_images_rails -``` - -And `application.css` +Include the supplied css asset in the manifest, `application.css`: ```css *= require lazy_images_rails @@ -38,14 +28,6 @@ Add an image in a view, `index.html.erb`: <%= image_tag 'foo.png' %> ``` -And then trigger the lazy-load by initializing the client side of things on DOM loaded, e.g.: - -```javascript -yourDomReadyFunction(function() { - LazyImagesRails.init(); -}); -``` - In case an image needs to be inserted without a placeholder, since the `image_tag` helper has been aliased, accessing the unmodified helper is done with the suffix: ```erb @@ -73,7 +55,7 @@ So setting this attribute will affect the placeholder as well and set width and The default [placeholder image](https://github.com/rhardih/lazy-images-rails/blob/master/app/assets/images/placeholder.svg) is a simple graphic of a mountain and a moon. Both have been supplied with targetable classes for individual styling. Create a custom rule with these classes, e.g. (scss): -```css +```scss .rli-wrapper { .rli-placeholder { background: #fff; @@ -107,20 +89,6 @@ Here assuming you have placed your custom placeholder in `app/assets/images/cust rake test ``` -### Clientside tests - -lazy-images-rails uses teaspoon for testing the clientside javascript: - -```bash -cd test/dummy && rake teaspoon -``` - -Alternatively it can run directly in the browser, by starting the dummy app and visiting the test harness page [http://localhost:3000](http://localhost:3000): - -```bash -cd test/dummy && bin/rails s -``` - ## License [MIT-LICENSE](https://github.com/rhardih/lazy-images-rails/blob/master/MIT-LICENSE) | http://rhardih.mit-license.org diff --git a/app/assets/javascripts/lazy_images_rails.js.coffee b/app/assets/javascripts/lazy_images_rails.js.coffee deleted file mode 100644 index 10472c6..0000000 --- a/app/assets/javascripts/lazy_images_rails.js.coffee +++ /dev/null @@ -1,26 +0,0 @@ -class @LazyImagesRails - @init: (all_replaced) -> - replace_with_image = (placeholder) => - noscript = placeholder.querySelector('noscript') - method = if 'innerText' in noscript then 'innerText' else 'textContent' - content = noscript[method] - - tmp = document.createElement('div') - tmp.innerHTML = content # will trigger download of image - img = tmp.querySelector('img') - - done = --@placeholder_count == 0 && all_replaced? && - typeof all_replaced == 'function' - - # Race condition: - # Event attachment happens after the download triggers above. - # If image loads before getting here, the callback never runs. - img.onload = => - placeholder.parentNode.replaceChild(img, placeholder) - all_replaced.call() if done - - placeholders = document.querySelectorAll('div[data-rli-placeholder]') - @placeholder_count = placeholders.length - - for placeholder in placeholders - replace_with_image placeholder diff --git a/lazy_images-rails.gemspec b/lazy_images-rails.gemspec index 60ae38f..81e9a83 100644 --- a/lazy_images-rails.gemspec +++ b/lazy_images-rails.gemspec @@ -10,11 +10,10 @@ Gem::Specification.new do |s| s.authors = ["René Hansen"] s.email = ["renehh@gmail.com"] s.homepage = "https://github.com/rhardih/lazy-images-rails" - s.summary = "Lazy loaded images with placeholders and noscript fallback" + s.summary = "Simple placeholders for images" s.description = "lazy-images-rails is a rails plugin that augments the " \ - "standard image_tag helper to provide instant placeholder images while " \ - "the actual image is being lazy loaded. A noscript fallback is provided " \ - "along with the placeholder for non-js browsers." + "standard image_tag helper to provide instant placeholders while the" \ + "actual image is still being fetched." s.license = "MIT" s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE"] diff --git a/lib/lazy_images/rails/placeholder.rb b/lib/lazy_images/rails/placeholder.rb new file mode 100644 index 0000000..e870554 --- /dev/null +++ b/lib/lazy_images/rails/placeholder.rb @@ -0,0 +1,35 @@ +require 'nokogiri' + +module LazyImages + module Rails + class Placeholder + include ActionView::Helpers::AssetTagHelper + + attr_reader :svg + attr_reader :options + + def initialize(svg, options) + @svg = svg + @options = options + end + + def to_s + if options[:size] + # N. B. extract_dimensions is a private method from + # actionview/lib/action_view/helpers/asset_tag_helper.rb + # beware of breakage + options[:width], options[:height] = + extract_dimensions(options.delete(:size)) + + fragment = Nokogiri::XML::DocumentFragment.parse(svg) + fragment.first_element_child['width'] = options[:width] + fragment.first_element_child['height'] = options[:height] + + fragment.to_s + else + svg + end + end + end + end +end diff --git a/lib/lazy_images/rails/tag_helper.rb b/lib/lazy_images/rails/tag_helper.rb index b4c05be..1788564 100644 --- a/lib/lazy_images/rails/tag_helper.rb +++ b/lib/lazy_images/rails/tag_helper.rb @@ -1,4 +1,4 @@ -require 'nokogiri' +require 'lazy_images/rails/placeholder' module LazyImages module Rails @@ -10,29 +10,18 @@ def self.included(base) end def image_tag_with_lazy_images(source, options={}) - src = path_to_image(source) - data = { rli_image_src: src, rli_placeholder: true } - placeholder = LazyImages::Rails.placeholder.dup + options.merge!( + class: "#{options[:class]} rli-image", + src: path_to_image(source) + ) - if options[:size] - # N. B. extract_dimensions is a private method from - # actionview/lib/action_view/helpers/asset_tag_helper.rb - # beware of breakage - options[:width], options[:height] = extract_dimensions(options.delete(:size)) + placeholder = LazyImages::Rails::Placeholder.new( + LazyImages::Rails.placeholder, options + ) - fragment = Nokogiri::XML::DocumentFragment.parse(placeholder) - fragment.first_element_child.set_attribute('width', options[:width]) - fragment.first_element_child.set_attribute('height', options[:height]) - - placeholder = fragment.to_s - end - - content_tag(:div, data: data, class: 'rli-wrapper') do - placeholder.html_safe + content_tag(:noscript) do - options[:src] = src - options[:class] = "#{options[:class]} rli-image" + content_tag(:div, class: 'rli-wrapper') do + placeholder.to_s.html_safe + image_tag_without_lazy_images(source, options) - end end end end diff --git a/test/dummy/public/foo.png b/test/dummy/public/foo.png deleted file mode 100644 index 91a99b9..0000000 Binary files a/test/dummy/public/foo.png and /dev/null differ diff --git a/test/dummy/spec/javascripts/lazy_images_rails_spec.coffee b/test/dummy/spec/javascripts/lazy_images_rails_spec.coffee deleted file mode 100644 index 91c6a9b..0000000 --- a/test/dummy/spec/javascripts/lazy_images_rails_spec.coffee +++ /dev/null @@ -1,35 +0,0 @@ -#= require lazy_images_rails - -describe "RailsLazyImages", -> - it "is defined", -> - expect(LazyImagesRails).toBeDefined() - - describe "init()", -> - fixture.set '
- - -
' - - beforeEach (done) -> - LazyImagesRails.init(done) - - it "removes all placeholders", -> - elements = document.querySelectorAll "div[data-rli-placeholder]" - expect(elements.length).toBe(0) - - it "replaces placholders with images", -> - expected = 'Foo' - expect(fixture.el.innerHTML).toBe(expected) - - describe "with dimensions on placeholder", -> - fixture.set '
- - -
' - - beforeEach (done) -> - LazyImagesRails.init(done) - - it "replaces images on page and preserves dimensions", -> - expected = 'Foo' - expect(fixture.el.innerHTML).toBe(expected) diff --git a/test/dummy/spec/javascripts/spec_helper.coffee b/test/dummy/spec/javascripts/spec_helper.coffee deleted file mode 100644 index 555eef4..0000000 --- a/test/dummy/spec/javascripts/spec_helper.coffee +++ /dev/null @@ -1,32 +0,0 @@ -# Teaspoon includes some support files, but you can use anything from your own support path too. -# require support/jasmine-jquery-1.7.0 -# require support/jasmine-jquery-2.0.0 -# require support/jasmine-jquery-2.1.0 -# require support/sinon -# require support/your-support-file -# -# PhantomJS (Teaspoons default driver) doesn't have support for Function.prototype.bind, which has caused confusion. -# Use this polyfill to avoid the confusion. -#= require support/bind-poly -# -# You can require your own javascript files here. By default this will include everything in application, however you -# may get better load performance if you require the specific files that are being used in the spec that tests them. -#= require application -# -# Deferring execution -# If you're using CommonJS, RequireJS or some other asynchronous library you can defer execution. Call -# Teaspoon.execute() after everything has been loaded. Simple example of a timeout: -# -# Teaspoon.defer = true -# setTimeout(Teaspoon.execute, 1000) -# -# Matching files -# By default Teaspoon will look for files that match _spec.{js,js.coffee,.coffee}. Add a filename_spec.js file in your -# spec path and it'll be included in the default suite automatically. If you want to customize suites, check out the -# configuration in teaspoon_env.rb -# -# Manifest -# If you'd rather require your spec files manually (to control order for instance) you can disable the suite matcher in -# the configuration and use this file as a manifest. -# -# For more information: http://github.com/modeset/teaspoon diff --git a/test/dummy/spec/teaspoon_env.rb b/test/dummy/spec/teaspoon_env.rb deleted file mode 100644 index 58f45ff..0000000 --- a/test/dummy/spec/teaspoon_env.rb +++ /dev/null @@ -1,178 +0,0 @@ -Teaspoon.configure do |config| - # Determines where the Teaspoon routes will be mounted. Changing this to "/jasmine" would allow you to browse to - # `http://localhost:3000/jasmine` to run your tests. - config.mount_at = "/teaspoon" - - # Specifies the root where Teaspoon will look for files. If you're testing an engine using a dummy application it can - # be useful to set this to your engines root (e.g. `Teaspoon::Engine.root`). - # Note: Defaults to `Rails.root` if nil. - config.root = nil - - # Paths that will be appended to the Rails assets paths - # Note: Relative to `config.root`. - config.asset_paths = ["spec/javascripts", "spec/javascripts/stylesheets"] - - # Fixtures are rendered through a controller, which allows using HAML, RABL/JBuilder, etc. Files in these paths will - # be rendered as fixtures. - config.fixture_paths = ["spec/javascripts/fixtures"] - - # SUITES - # - # You can modify the default suite configuration and create new suites here. Suites are isolated from one another. - # - # When defining a suite you can provide a name and a block. If the name is left blank, :default is assumed. You can - # omit various directives and the ones defined in the default suite will be used. - # - # To run a specific suite - # - in the browser: http://localhost/teaspoon/[suite_name] - # - with the rake task: rake teaspoon suite=[suite_name] - # - with the cli: teaspoon --suite=[suite_name] - config.suite do |suite| - # Specify the framework you would like to use. This allows you to select versions, and will do some basic setup for - # you -- which you can override with the directives below. This should be specified first, as it can override other - # directives. - # Note: If no version is specified, the latest is assumed. - # - # Versions: 1.3.1, 2.0.3, 2.1.3, 2.2.0 - suite.use_framework :jasmine, "2.2.0" - - # Specify a file matcher as a regular expression and all matching files will be loaded when the suite is run. These - # files need to be within an asset path. You can add asset paths using the `config.asset_paths`. - suite.matcher = "{spec/javascripts,app/assets}/**/*_spec.{js,js.coffee,coffee}" - - # Load additional JS files, but requiring them in your spec helper is the preferred way to do this. - #suite.javascripts = [] - - # You can include your own stylesheets if you want to change how Teaspoon looks. - # Note: Spec related CSS can and should be loaded using fixtures. - #suite.stylesheets = ["teaspoon"] - - # This suites spec helper, which can require additional support files. This file is loaded before any of your test - # files are loaded. - suite.helper = "spec_helper" - - # Partial to be rendered in the head tag of the runner. You can use the provided ones or define your own by creating - # a `_boot.html.erb` in your fixtures path, and adjust the config to `"/boot"` for instance. - # - # Available: boot, boot_require_js - suite.boot_partial = "boot" - - # Partial to be rendered in the body tag of the runner. You can define your own to create a custom body structure. - suite.body_partial = "body" - - # Hooks allow you to use `Teaspoon.hook("fixtures")` before, after, or during your spec run. This will make a - # synchronous Ajax request to the server that will call all of the blocks you've defined for that hook name. - #suite.hook :fixtures, &proc{} - - # Determine whether specs loaded into the test harness should be embedded as individual script tags or concatenated - # into a single file. Similar to Rails' asset `debug: true` and `config.assets.debug = true` options. By default, - # Teaspoon expands all assets to provide more valuable stack traces that reference individual source files. - #suite.expand_assets = true - end - - # Example suite. Since we're just filtering to files already within the root test/javascripts, these files will also - # be run in the default suite -- but can be focused into a more specific suite. - #config.suite :targeted do |suite| - # suite.matcher = "spec/javascripts/targeted/*_spec.{js,js.coffee,coffee}" - #end - - # CONSOLE RUNNER SPECIFIC - # - # These configuration directives are applicable only when running via the rake task or command line interface. These - # directives can be overridden using the command line interface arguments or with ENV variables when using the rake - # task. - # - # Command Line Interface: - # teaspoon --driver=phantomjs --server-port=31337 --fail-fast=true --format=junit --suite=my_suite /spec/file_spec.js - # - # Rake: - # teaspoon DRIVER=phantomjs SERVER_PORT=31337 FAIL_FAST=true FORMATTERS=junit suite=my_suite - - # Specify which headless driver to use. Supports PhantomJS and Selenium Webdriver. - # - # Available: :phantomjs, :selenium, :capybara_webkit - # PhantomJS: https://github.com/modeset/teaspoon/wiki/Using-PhantomJS - # Selenium Webdriver: https://github.com/modeset/teaspoon/wiki/Using-Selenium-WebDriver - # Capybara Webkit: https://github.com/modeset/teaspoon/wiki/Using-Capybara-Webkit - #config.driver = :phantomjs - - # Specify additional options for the driver. - # - # PhantomJS: https://github.com/modeset/teaspoon/wiki/Using-PhantomJS - # Selenium Webdriver: https://github.com/modeset/teaspoon/wiki/Using-Selenium-WebDriver - # Capybara Webkit: https://github.com/modeset/teaspoon/wiki/Using-Capybara-Webkit - #config.driver_options = nil - - # Specify the timeout for the driver. Specs are expected to complete within this time frame or the run will be - # considered a failure. This is to avoid issues that can arise where tests stall. - #config.driver_timeout = 180 - - # Specify a server to use with Rack (e.g. thin, mongrel). If nil is provided Rack::Server is used. - #config.server = nil - - # Specify a port to run on a specific port, otherwise Teaspoon will use a random available port. - #config.server_port = nil - - # Timeout for starting the server in seconds. If your server is slow to start you may have to bump this, or you may - # want to lower this if you know it shouldn't take long to start. - #config.server_timeout = 20 - - # Force Teaspoon to fail immediately after a failing suite. Can be useful to make Teaspoon fail early if you have - # several suites, but in environments like CI this may not be desirable. - #config.fail_fast = true - - # Specify the formatters to use when outputting the results. - # Note: Output files can be specified by using `"junit>/path/to/output.xml"`. - # - # Available: :dot, :clean, :documentation, :json, :junit, :pride, :rspec_html, :snowday, :swayze_or_oprah, :tap, :tap_y, :teamcity - #config.formatters = [:dot] - - # Specify if you want color output from the formatters. - #config.color = true - - # Teaspoon pipes all console[log/debug/error] to $stdout. This is useful to catch places where you've forgotten to - # remove them, but in verbose applications this may not be desirable. - #config.suppress_log = false - - # COVERAGE REPORTS / THRESHOLD ASSERTIONS - # - # Coverage reports requires Istanbul (https://github.com/gotwarlost/istanbul) to add instrumentation to your code and - # display coverage statistics. - # - # Coverage configurations are similar to suites. You can define several, and use different ones under different - # conditions. - # - # To run with a specific coverage configuration - # - with the rake task: rake teaspoon USE_COVERAGE=[coverage_name] - # - with the cli: teaspoon --coverage=[coverage_name] - - # Specify that you always want a coverage configuration to be used. Otherwise, specify that you want coverage - # on the CLI. - # Set this to "true" or the name of your coverage config. - #config.use_coverage = nil - - # You can have multiple coverage configs by passing a name to config.coverage. - # e.g. config.coverage :ci do |coverage| - # The default coverage config name is :default. - config.coverage do |coverage| - # Which coverage reports Istanbul should generate. Correlates directly to what Istanbul supports. - # - # Available: text-summary, text, html, lcov, lcovonly, cobertura, teamcity - #coverage.reports = ["text-summary", "html"] - - # The path that the coverage should be written to - when there's an artifact to write to disk. - # Note: Relative to `config.root`. - #coverage.output_path = "coverage" - - # Assets to be ignored when generating coverage reports. Accepts an array of filenames or regular expressions. The - # default excludes assets from vendor, gems and support libraries. - #coverage.ignore = [%r{/lib/ruby/gems/}, %r{/vendor/assets/}, %r{/support/}, %r{/(.+)_helper.}] - - # Various thresholds requirements can be defined, and those thresholds will be checked at the end of a run. If any - # aren't met the run will fail with a message. Thresholds can be defined as a percentage (0-100), or nil. - #coverage.statements = nil - #coverage.functions = nil - #coverage.branches = nil - #coverage.lines = nil - end -end diff --git a/test/fixtures/_image_tag_output.html b/test/fixtures/_image_tag_output.html index 4098022..7bcb6ee 100644 --- a/test/fixtures/_image_tag_output.html +++ b/test/fixtures/_image_tag_output.html @@ -1,5 +1,5 @@ -
+
-
+Foo
diff --git a/test/fixtures/_image_tag_output_with_size.html b/test/fixtures/_image_tag_output_with_size.html index a962da0..a760e04 100644 --- a/test/fixtures/_image_tag_output_with_size.html +++ b/test/fixtures/_image_tag_output_with_size.html @@ -1,5 +1,5 @@ -
+
-
+Foo
diff --git a/test/lazy_images_test.rb b/test/lazy_images_test.rb index 2a97915..b9cc934 100644 --- a/test/lazy_images_test.rb +++ b/test/lazy_images_test.rb @@ -5,13 +5,13 @@ class LazyImagesTest < ActionView::TestCase assert_kind_of Module, LazyImages end - test "image_tag outputs wrapped image with placeholder and noscript" do + test "image_tag outputs wrapped image with placeholder" do expected = fixture("_image_tag_output.html") assert_dom_equal(expected, image_tag('foo.png')) end - test 'image_tag adds dimensions to placeholder and noscript as well' do + test 'image_tag adds dimensions to placeholder as well' do expected = fixture("_image_tag_output_with_size.html") assert_dom_equal(expected, image_tag('foo.png', size: '123x456'))