<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://sourceworlds.wiki/index.php?action=history&amp;feed=atom&amp;title=MediaWiki%3AGadget-FreezeGifs.js</id>
	<title>MediaWiki:Gadget-FreezeGifs.js - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://sourceworlds.wiki/index.php?action=history&amp;feed=atom&amp;title=MediaWiki%3AGadget-FreezeGifs.js"/>
	<link rel="alternate" type="text/html" href="https://sourceworlds.wiki/index.php?title=MediaWiki:Gadget-FreezeGifs.js&amp;action=history"/>
	<updated>2026-11-09T22:48:04Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.46.0</generator>
	<entry>
		<id>https://sourceworlds.wiki/index.php?title=MediaWiki:Gadget-FreezeGifs.js&amp;diff=1617&amp;oldid=prev</id>
		<title>Vizzys: Protected &quot;MediaWiki:Gadget-FreezeGifs.js&quot; ([Edit=Allow only administrators] (indefinite) [Move=Allow only administrators] (indefinite))</title>
		<link rel="alternate" type="text/html" href="https://sourceworlds.wiki/index.php?title=MediaWiki:Gadget-FreezeGifs.js&amp;diff=1617&amp;oldid=prev"/>
		<updated>2026-10-19T23:46:23Z</updated>

		<summary type="html">&lt;p&gt;Protected &amp;quot;&lt;a href=&quot;/MediaWiki:Gadget-FreezeGifs.js&quot; title=&quot;MediaWiki:Gadget-FreezeGifs.js&quot;&gt;MediaWiki:Gadget-FreezeGifs.js&lt;/a&gt;&amp;quot; ([Edit=Allow only administrators] (indefinite) [Move=Allow only administrators] (indefinite))&lt;/p&gt;
&lt;table style=&quot;background-color: #fff; color: #202122;&quot; data-mw-interface=&quot;&quot;&gt;
				&lt;tr class=&quot;diff-title&quot; lang=&quot;en&quot;&gt;
				&lt;td colspan=&quot;1&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;← Older revision&lt;/td&gt;
				&lt;td colspan=&quot;1&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;Revision as of 23:46, 19 October 2026&lt;/td&gt;
				&lt;/tr&gt;&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-notice&quot; lang=&quot;en&quot;&gt;&lt;div class=&quot;mw-diff-empty&quot;&gt;(No difference)&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;</summary>
		<author><name>Vizzys</name></author>
	</entry>
	<entry>
		<id>https://sourceworlds.wiki/index.php?title=MediaWiki:Gadget-FreezeGifs.js&amp;diff=1616&amp;oldid=prev</id>
		<title>Vizzys: Created page with &quot;/**  * Gadget-FreezeGifs.js  *  * This gadget finds animated GIFs and autoplaying videos on a page,  * freezes them, and overlays a play icon. The animation or video plays  * only when the user hovers their mouse over the element.  *  * - Freezes GIFs by drawing their first frame to a &lt;canvas&gt; element.  * - Correctly handles lazy-loaded images and gallery thumbnails.  * - Excludes specific areas (like .charbox) from being processed.  */ (function (mw, $) {  	// A flag to...&quot;</title>
		<link rel="alternate" type="text/html" href="https://sourceworlds.wiki/index.php?title=MediaWiki:Gadget-FreezeGifs.js&amp;diff=1616&amp;oldid=prev"/>
		<updated>2026-10-19T23:46:16Z</updated>

		<summary type="html">&lt;p&gt;Created page with &amp;quot;&lt;span class=&quot;autocomment&quot;&gt;*  * Gadget-FreezeGifs.js  *  * This gadget finds animated GIFs and autoplaying videos on a page,  * freezes them, and overlays a play icon. The animation or video plays  * only when the user hovers their mouse over the element.  *  * - Freezes GIFs by drawing their first frame to a &amp;lt;canvas&amp;gt; element.  * - Correctly handles lazy-loaded images and gallery thumbnails.  * - Excludes specific areas (like .charbox) from being processed.: &lt;/span&gt; (function (mw, $) {  	// A flag to...&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;/**&lt;br /&gt;
 * Gadget-FreezeGifs.js&lt;br /&gt;
 *&lt;br /&gt;
 * This gadget finds animated GIFs and autoplaying videos on a page,&lt;br /&gt;
 * freezes them, and overlays a play icon. The animation or video plays&lt;br /&gt;
 * only when the user hovers their mouse over the element.&lt;br /&gt;
 *&lt;br /&gt;
 * - Freezes GIFs by drawing their first frame to a &amp;lt;canvas&amp;gt; element.&lt;br /&gt;
 * - Correctly handles lazy-loaded images and gallery thumbnails.&lt;br /&gt;
 * - Excludes specific areas (like .charbox) from being processed.&lt;br /&gt;
 */&lt;br /&gt;
(function (mw, $) {&lt;br /&gt;
&lt;br /&gt;
	// A flag to ensure the script doesn&amp;#039;t run multiple times on the same page load.&lt;br /&gt;
	if (window.PAUSE_ANIMATIONS_GADGET_LOADED) {&lt;br /&gt;
		return;&lt;br /&gt;
	}&lt;br /&gt;
	window.PAUSE_ANIMATIONS_GADGET_LOADED = true;&lt;br /&gt;
&lt;br /&gt;
	var config = {&lt;br /&gt;
		containerClass: &amp;#039;pa-container&amp;#039;,&lt;br /&gt;
		overlayClass: &amp;#039;pa-overlay&amp;#039;,&lt;br /&gt;
		playIconClass: &amp;#039;pa-play-icon&amp;#039;,&lt;br /&gt;
		processedClass: &amp;#039;pa-processed&amp;#039;,&lt;br /&gt;
		pendingClass: &amp;#039;pa-pending&amp;#039;, // Class to prevent race conditions on lazy-loaded images&lt;br /&gt;
		staticCanvasClass: &amp;#039;pa-static-canvas&amp;#039;,&lt;br /&gt;
		animatedGifClass: &amp;#039;pa-animated-gif&amp;#039;,&lt;br /&gt;
		// CSS styles are injected dynamically.&lt;br /&gt;
		css:&lt;br /&gt;
			// 1. The container class is added to the IMG&amp;#039;s existing parent, setting up the positioning context.&lt;br /&gt;
			&amp;#039;.pa-container { position: relative !important; }&amp;#039; +&lt;br /&gt;
			// 2. The original animated GIF remains in the normal layout flow but is made invisible.&lt;br /&gt;
			&amp;#039;.pa-container &amp;gt; img.&amp;#039; + &amp;#039;pa-animated-gif&amp;#039; + &amp;#039; { visibility: hidden !important; }&amp;#039; +&lt;br /&gt;
			// 3. The canvas and overlay are layered on top using absolute positioning. They will fill the container.&lt;br /&gt;
			&amp;#039;.pa-container &amp;gt; .pa-static-canvas, .pa-container &amp;gt; .pa-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }&amp;#039; +&lt;br /&gt;
			// 4. The canvas is visible by default, covering the space of the hidden GIF.&lt;br /&gt;
			&amp;#039;.pa-container &amp;gt; .pa-static-canvas { visibility: visible; z-index: 1; }&amp;#039; +&lt;br /&gt;
			// 5. The overlay sits on the highest layer.&lt;br /&gt;
			&amp;#039;.pa-overlay { z-index: 2; background-color: rgba(0, 0, 0, 0.3); display: flex; align-items: center; justify-content: center; cursor: pointer; transition: opacity 0.2s ease-in-out; opacity: 1; pointer-events: none; }&amp;#039; +&lt;br /&gt;
			&amp;#039;.pa-play-icon { box-sizing: border-box; width: 0; height: 30px; border-color: transparent transparent transparent white; border-style: solid; border-width: 15px 0 15px 25px; }&amp;#039; +&lt;br /&gt;
			// 6. On hover, fade out the overlay.&lt;br /&gt;
			&amp;#039;.pa-container:hover .pa-overlay { opacity: 0; }&amp;#039; +&lt;br /&gt;
			// 7. On hover, make the original animated GIF visible again...&lt;br /&gt;
			&amp;#039;.pa-container:hover &amp;gt; img.&amp;#039; + &amp;#039;pa-animated-gif&amp;#039; + &amp;#039; { visibility: visible !important; }&amp;#039; +&lt;br /&gt;
			// 8. ...and hide the static canvas.&lt;br /&gt;
			&amp;#039;.pa-container:hover &amp;gt; .&amp;#039; + &amp;#039;pa-static-canvas&amp;#039; + &amp;#039; { visibility: hidden !important; }&amp;#039;&lt;br /&gt;
	};&lt;br /&gt;
&lt;br /&gt;
	/**&lt;br /&gt;
	 * Finds and freezes all autoplaying videos.&lt;br /&gt;
	 */&lt;br /&gt;
	function handleVideos() {&lt;br /&gt;
		$(&amp;#039;video[autoplay]&amp;#039;).not(&amp;#039;.&amp;#039; + config.processedClass).each(function () {&lt;br /&gt;
			var $video = $(this);&lt;br /&gt;
			this.pause();&lt;br /&gt;
			this.currentTime = 0;&lt;br /&gt;
			$video.addClass(config.processedClass).removeAttr(&amp;#039;autoplay&amp;#039;).attr(&amp;#039;preload&amp;#039;, &amp;#039;metadata&amp;#039;).prop(&amp;#039;loop&amp;#039;, true);&lt;br /&gt;
			$video.wrap($(&amp;#039;&amp;lt;div&amp;gt;&amp;#039;).addClass(config.containerClass));&lt;br /&gt;
			var $container = $video.parent();&lt;br /&gt;
			$container.append($(&amp;#039;&amp;lt;div&amp;gt;&amp;#039;).addClass(config.overlayClass).append($(&amp;#039;&amp;lt;div&amp;gt;&amp;#039;).addClass(config.playIconClass)));&lt;br /&gt;
			if ($video.css(&amp;#039;float&amp;#039;) !== &amp;#039;none&amp;#039;) {&lt;br /&gt;
				$container.css(&amp;#039;float&amp;#039;, $video.css(&amp;#039;float&amp;#039;));&lt;br /&gt;
				$video.css(&amp;#039;float&amp;#039;, &amp;#039;none&amp;#039;);&lt;br /&gt;
			}&lt;br /&gt;
			$container.on(&amp;#039;mouseenter&amp;#039;, function () {&lt;br /&gt;
				this.querySelector(&amp;#039;video&amp;#039;).play().catch(function() {&lt;br /&gt;
					this.querySelector(&amp;#039;video&amp;#039;).muted = true;&lt;br /&gt;
					this.querySelector(&amp;#039;video&amp;#039;).play();&lt;br /&gt;
				});&lt;br /&gt;
			}).on(&amp;#039;mouseleave&amp;#039;, function () {&lt;br /&gt;
				var videoEl = this.querySelector(&amp;#039;video&amp;#039;);&lt;br /&gt;
				videoEl.pause();&lt;br /&gt;
				videoEl.currentTime = 0;&lt;br /&gt;
			});&lt;br /&gt;
		});&lt;br /&gt;
	}&lt;br /&gt;
	&lt;br /&gt;
	/**&lt;br /&gt;
	 * Creates a static canvas and layers it correctly without breaking layouts.&lt;br /&gt;
	 */&lt;br /&gt;
	function createStaticCanvas(imgElement) {&lt;br /&gt;
		var $img = $(imgElement);&lt;br /&gt;
		var $parent = $img.parent();&lt;br /&gt;
	&lt;br /&gt;
		// For galleries, the image may be wrapped in an extra span or anchor&lt;br /&gt;
		if ($parent.hasClass(&amp;#039;galleryfilename&amp;#039;) || $parent.hasClass(&amp;#039;thumb&amp;#039;) || $parent.hasClass(&amp;#039;gallerybox&amp;#039;)) {&lt;br /&gt;
			$parent = $img.closest(&amp;#039;.thumb, .gallerybox, .galleryfilename&amp;#039;);&lt;br /&gt;
		}&lt;br /&gt;
	&lt;br /&gt;
		$img.removeClass(config.pendingClass).addClass(config.processedClass);&lt;br /&gt;
	&lt;br /&gt;
		// Create canvas&lt;br /&gt;
		var canvas = document.createElement(&amp;#039;canvas&amp;#039;);&lt;br /&gt;
		canvas.width = imgElement.naturalWidth;&lt;br /&gt;
		canvas.height = imgElement.naturalHeight;&lt;br /&gt;
		canvas.getContext(&amp;#039;2d&amp;#039;).drawImage(imgElement, 0, 0);&lt;br /&gt;
	&lt;br /&gt;
		$img.addClass(config.animatedGifClass);&lt;br /&gt;
		$(canvas).addClass(config.staticCanvasClass);&lt;br /&gt;
	&lt;br /&gt;
		// Ensure container is positionable&lt;br /&gt;
		$parent.css({ position: &amp;#039;relative&amp;#039;, display: &amp;#039;inline-block&amp;#039; }).addClass(config.containerClass);&lt;br /&gt;
	&lt;br /&gt;
		// Insert canvas and overlay on top&lt;br /&gt;
		$parent.append(canvas);&lt;br /&gt;
		$parent.append($(&amp;#039;&amp;lt;div&amp;gt;&amp;#039;).addClass(config.overlayClass).append($(&amp;#039;&amp;lt;div&amp;gt;&amp;#039;).addClass(config.playIconClass)));&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	/**&lt;br /&gt;
	 * Finds and freezes all animated GIFs.&lt;br /&gt;
	 */&lt;br /&gt;
	function handleGifs() {&lt;br /&gt;
		// Select GIFs that aren&amp;#039;t in a charbox and haven&amp;#039;t been marked as pending or processed.&lt;br /&gt;
		$(&amp;#039;img[src$=&amp;quot;.gif&amp;quot;]&amp;#039;)&lt;br /&gt;
			.not(&amp;#039;.charbox img, .&amp;#039; + config.processedClass + &amp;#039;, .&amp;#039; + config.pendingClass)&lt;br /&gt;
			.each(function () {&lt;br /&gt;
				var imageElement = this;&lt;br /&gt;
				&lt;br /&gt;
				if (imageElement.complete &amp;amp;&amp;amp; imageElement.naturalWidth &amp;gt; 0) {&lt;br /&gt;
					// If image is already loaded, process it immediately.&lt;br /&gt;
					createStaticCanvas(imageElement);&lt;br /&gt;
				} else {&lt;br /&gt;
					// If image is not loaded, mark it as &amp;#039;pending&amp;#039; immediately.&lt;br /&gt;
					var $img = $(imageElement);&lt;br /&gt;
					$img.addClass(config.pendingClass);&lt;br /&gt;
					&lt;br /&gt;
					// Then, attach a ONE-TIME listener to process it when it&amp;#039;s ready.&lt;br /&gt;
					$img.one(&amp;#039;load&amp;#039;, function () {&lt;br /&gt;
						createStaticCanvas(this);&lt;br /&gt;
					}).one(&amp;#039;error&amp;#039;, function() {&lt;br /&gt;
						// If the image fails, just mark it as processed so we don&amp;#039;t try again.&lt;br /&gt;
						$(this).removeClass(config.pendingClass).addClass(config.processedClass);&lt;br /&gt;
					});&lt;br /&gt;
				}&lt;br /&gt;
			});&lt;br /&gt;
			&lt;br /&gt;
			// Also check gallery images specifically&lt;br /&gt;
			$(&amp;#039;.gallerybox img[src$=&amp;quot;.gif&amp;quot;]&amp;#039;)&lt;br /&gt;
				.not(&amp;#039;.&amp;#039; + config.processedClass + &amp;#039;, .&amp;#039; + config.pendingClass)&lt;br /&gt;
				.each(function () {&lt;br /&gt;
					var imageElement = this;&lt;br /&gt;
			&lt;br /&gt;
					if (imageElement.complete &amp;amp;&amp;amp; imageElement.naturalWidth &amp;gt; 0) {&lt;br /&gt;
						createStaticCanvas(imageElement);&lt;br /&gt;
					} else {&lt;br /&gt;
						var $img = $(imageElement);&lt;br /&gt;
						$img.addClass(config.pendingClass);&lt;br /&gt;
			&lt;br /&gt;
						$img.one(&amp;#039;load&amp;#039;, function () {&lt;br /&gt;
							createStaticCanvas(this);&lt;br /&gt;
						}).one(&amp;#039;error&amp;#039;, function () {&lt;br /&gt;
							$(this).removeClass(config.pendingClass).addClass(config.processedClass);&lt;br /&gt;
						});&lt;br /&gt;
					}&lt;br /&gt;
				});&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// Main execution block.&lt;br /&gt;
	mw.loader.using([&amp;#039;mediawiki.util&amp;#039;], function () {&lt;br /&gt;
		$(function () {&lt;br /&gt;
			mw.util.addCSS(config.css);&lt;br /&gt;
			&lt;br /&gt;
			var runPause = function() {&lt;br /&gt;
				handleVideos();&lt;br /&gt;
				handleGifs();&lt;br /&gt;
			};&lt;br /&gt;
&lt;br /&gt;
			runPause();&lt;br /&gt;
&lt;br /&gt;
			var targetNode = document.getElementById(&amp;#039;mw-content-text&amp;#039;);&lt;br /&gt;
			if (targetNode) {&lt;br /&gt;
				new MutationObserver(runPause).observe(targetNode, {&lt;br /&gt;
					childList: true,&lt;br /&gt;
					subtree: true&lt;br /&gt;
				});&lt;br /&gt;
			}&lt;br /&gt;
		});&lt;br /&gt;
	});&lt;br /&gt;
}(mediaWiki, jQuery));&lt;/div&gt;</summary>
		<author><name>Vizzys</name></author>
	</entry>
</feed>