PageSourceSearch

https://suite.st/docs/js/freezeframe.js

js suite.st collected 2026-10-01 11:31:15 UTC 12,425 bytes, 377 lines download raw bytes

1// pass references around in a cleaner way
2// remove jquery dependency
3
4var freezeframe = (function($) {
5  var images, options, is_touch_device;
6
7  //////////////////////////////////////////////////////////////////////////////
8  //                                                                          //
9  //  Private Methods                                                         //
10  //                                                                          //
11  //////////////////////////////////////////////////////////////////////////////
12
13  // decorated console.warn message
14  var warn = function(_message) {
15    console.warn("✨ freezeframe.js ✨ : " + _message);
16  };
17
18  // does freezeframe instance have any captured images?
19  var has_images = function() {
20    return this.images.length == 0 ? false : true;
21  };
22
23  // filter captured images by selector and warn if none found
24  var filter = function(_selector, _images) {
25    var filtered_images;
26
27    if (_selector != undefined && _images.length > 1) {
28      filtered_images = _images.filter($(_selector));
29      if (filtered_images.length == 0) {
30        warn("no images found for selector '" + _selector + "'");
31        return false;
32      }
33    } else {
34      filtered_images = _images;
35    }
36
37    return filtered_images;
38  };
39
40  // reset .gif to first frame and write to canvas
41  var process = function($_image) {
42    var ff = this,
43      $canvas = $_image.siblings("canvas"),
44      transitionEnd =
45        "transitionend webkitTransitionEnd oTransitionEnd otransitionend",
46      image_width = $_image[0].clientWidth,
47      image_height = $_image[0].clientHeight;
48
49    $canvas.attr({
50      width: image_width,
51      height: image_height
52    });
53
54    context = $canvas[0].getContext("2d");
55    context.drawImage($_image[0], 0, 0, image_width, image_height);
56
57    $canvas.addClass("ff-canvas-ready").on(transitionEnd, function() {
58      $(this).off(transitionEnd);
59      $_image.addClass("ff-image-ready");
60    });
61  };
62
63  //////////////////////////////////////////////////////////////////////////////
64  //                                                                          //
65  //  Constructor                                                             //
66  //                                                                          //
67  //////////////////////////////////////////////////////////////////////////////
68  function freezeframe(_options) {
69    var options;
70
71    // default options
72    this.options = {
73      selector: ".freezeframe",
74      animation_play_duration: 5000,
75      non_touch_device_trigger_event: "hover"
76    };
77
78    // new selector as string
79    options = typeof _options == "string" ? { selector: _options } : _options;
80
81    // new options
82    if (options) {
83      for (attribute in options) {
84        if (attribute in this.options) {
85          this.options[attribute] = options[attribute];
86        } else {
87          warn(attribute + "not a valid option");
88        }
89      }
90    }
91
92    // is this a touch device?
93    this.is_touch_device =
94      "ontouchstart" in window || "onmsgesturechange" in window;
95  }
96
97  //////////////////////////////////////////////////////////////////////////////
98  //                                                                          //
99  //  Capture Images                                                          //
100  //                                                                          //
101  //////////////////////////////////////////////////////////////////////////////
102  freezeframe.prototype.capture = function(_selector) {
103    var selector;
104
105    // Passed in string or default string
106    if (_selector !== undefined) {
107      selector = _selector;
108    } else if (this.options.selector !== undefined) {
109      selector = this.options.selector;
110    } else {
111      warn("no selector passed to capture function or set in options");
112      return false;
113    }
114
115    // Empty jQuery object to add into
116    if (this.images == undefined) {
117      this.images = $();
118    }
119
120    // Add new selection, jQuery keeps it non redundant
121    this.images = this.images.add($("img" + selector));
122
123    // Get non gifs outta there
124    for (i = 0; i < this.images.length; i++) {
125      if (
126        this.images[i].src
127          .split(".")
128          .pop()
129          .toLowerCase()
130          .substring(0, 3) !== "gif"
131      ) {
132        this.images.splice(i, 1);
133      }
134    }
135
136    // If nothing was found, throw a fit
137    if (this.images.length == 0) {
138      console.warn(
139        'freezeframe : no gifs found for selector "' + selector + '"'
140      );
141      return false;
142    }
143
144    return this;
145  };
146
147  //////////////////////////////////////////////////////////////////////////////
148  //                                                                          //
149  //  Setup Elements                                                          //
150  //                                                                          //
151  //////////////////////////////////////////////////////////////////////////////
152  freezeframe.prototype.setup = function(_selector) {
153    var ff = this,
154      setup_required = this.images.not(".ff-setup"),
155      container_classnames = ["ff-container"];
156
157    if (!has_images.call(ff)) {
158      warn("unable to run setup(), no images captured");
159      return false;
160    } else if (setup_required.length == 0) {
161      warn("unable to run setup(), no images require setup");
162      return false;
163    }
164
165    filter.call(ff, _selector, setup_required).each(function(e) {
166      var $image = $(this);
167
168      $image.addClass("ff-setup ff-image");
169
170      if ($image.hasClass("freezeframe-responsive")) {
171        container_classnames.push("ff-responsive");
172      }
173
174      $canvas = $("<canvas />", {
175        class: "ff-canvas"
176      })
177        .attr({
178          width: 0,
179          height: 0
180        })
181        .insertBefore($image);
182
183      $image.add($canvas).wrapAll(
184        $("<div />", {
185          class: container_classnames.join(" ")
186        })
187      );
188    });
189
190    imagesLoaded(setup_required).on("progress", function(instance, image) {
191      process.call(ff, $(image.img));
192    });
193
194    return this;
195  };
196
197  //////////////////////////////////////////////////////////////////////////////
198  //                                                                          //
199  //  Attach Hover / Click Events                                             //
200  //                                                                          //
201  //////////////////////////////////////////////////////////////////////////////
202  freezeframe.prototype.attach = function(_selector) {
203    var ff = this,
204      click_timeout,
205      images;
206
207    if (!has_images.call(ff)) {
208      warn("unable to run attach(), no images captured");
209      return false;
210    }
211
212    filter.call(ff, _selector, ff.images).each(function(e) {
213      var $image = $(this);
214      var $canvas = $(this).siblings("canvas");
215
216      // hover /////////////////////////////////////////////////////////////////
217      if (
218        (!ff.is_touch_device &&
219          ff.options.non_touch_device_trigger_event == "hover") ||
220        ff.is_touch_device
221      ) {
222        $image.mouseenter(function() {
223          (function() {
224            if ($image.hasClass("ff-image-ready")) {
225              $image.attr("src", $image[0].src);
226              $canvas
227                .removeClass("ff-canvas-ready")
228                .addClass("ff-canvas-active");
229            }
230          })();
231        });
232
233        $image.mouseleave(function() {
234          (function() {
235            if ($image.hasClass("ff-image-ready")) {
236              $canvas
237                .removeClass("ff-canvas-active")
238                .addClass("ff-canvas-ready");
239            }
240          })();
241        });
242      }
243
244      // click /////////////////////////////////////////////////////////////////
245      if (
246        (!ff.is_touch_device &&
247          ff.options.non_touch_device_trigger_event == "click") ||
248        ff.is_touch_device
249      ) {
250        var click_timeout;
251
252        $image.click(function() {
253          (function() {
254            var clicked = $canvas.hasClass("ff-canvas-active");
255
256            if ($image.hasClass("ff-image-ready")) {
257              if (clicked) {
258                if (ff.options.animation_play_duration != Infinity) {
259                  clearTimeout(click_timeout);
260                }
261
262                $canvas
263                  .removeClass("ff-canvas-active")
264                  .addClass("ff-canvas-ready");
265              } else {
266                $image.attr("src", $image[0].src);
267                $canvas
268                  .removeClass("ff-canvas-ready")
269                  .addClass("ff-canvas-active");
270
271                if (ff.options.animation_play_duration != Infinity) {
272                  click_timeout = setTimeout(function() {
273                    $canvas
274                      .removeClass("ff-canvas-active")
275                      .addClass("ff-canvas-ready");
276                  }, ff.options.animation_play_duration);
277                }
278              }
279            }
280          })();
281        });
282      }
283    });
284
285    return this;
286  };
287
288  //////////////////////////////////////////////////////////////////////////////
289  //                                                                          //
290  //  Trigger Animation                                                       //
291  //                                                                          //
292  //////////////////////////////////////////////////////////////////////////////
293  freezeframe.prototype.trigger = function(_selector) {
294    var ff = this,
295      errors = 0;
296
297    filter.call(ff, _selector, ff.images).each(function(e) {
298      if ($(this).hasClass("ff-image-ready")) {
299        $(this).attr("src", $(this)[0].src);
300        $(this)
301          .siblings("canvas")
302          .removeClass("ff-canvas-ready")
303          .addClass("ff-canvas-active");
304      } else {
305        warn("image not done processing ! " + $(this).attr("src"));
306        errors++;
307      }
308    });
309
310    return errors == 0 ? true : false;
311  };
312
313  //////////////////////////////////////////////////////////////////////////////
314  //                                                                          //
315  //  Release Animation                                                       //
316  //                                                                          //
317  //////////////////////////////////////////////////////////////////////////////
318  freezeframe.prototype.release = function(_selector) {
319    var ff = this,
320      errors = 0;
321
322    filter.call(ff, _selector, ff.images).each(function(e) {
323      if ($(this).hasClass("ff-image-ready")) {
324        $(this)
325          .siblings("canvas")
326          .removeClass("ff-canvas-active")
327          .addClass("ff-canvas-ready");
328      } else {
329        warn("image not done processing ! " + $(this).attr("src"));
330        errors++;
331      }
332    });
333
334    return errors == 0 ? true : false;
335  };
336
337  //////////////////////////////////////////////////////////////////////////////
338  //                                                                          //
339  //  Freeze Images                                                           //
340  //                                                                          //
341  //////////////////////////////////////////////////////////////////////////////
342  freezeframe.prototype.freeze = function() {
343    this.capture()
344      .setup()
345      .attach(); // ✨ tada ✨
346    return this;
347  };
348
349  return freezeframe;
350})(jQuery);
351
352// jQuery plugin
353$.fn.freezeframe = function(_options) {
354  if (this.length == 0) {
355    console.warn(
356      "✨ freezeframe.js ✨ : no images found for selector " + this.selector
357    );
358    return false;
359  }
360
361  var ff = new freezeframe(_options);
362
363  ff.images = this;
364
365  ff.setup().attach();
366
367  var self = this;
368  var methods = ["trigger", "release"];
369  methods.forEach(function(method) {
370    self[method] = function() {
371      ff[method](self.selector);
372      return self;
373    };
374  });
375
376  return this;
377};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.