Copy link to clipboard
Copied
For social media posts there are file size limits on GIFs. For instance, Facebook is 5MB. I typically will hit this target in the Save for Web panel by adjusting the dimensions of the GIF until the file size is under 5MB. This is a tedious process that can definitely be sped up. I suggest a tool that allows you to input the file size target. It then automatically adjusts the dimensions of the GIF accordingly.
Looks like the Optimise to File Size... feature does work for animated GIF files, however, it has a max limit of 2048K and it doesn't resize the px dimensions:
A custom script would be required to reduce dimensions to achieve a target size on drive, while retaining your other GIF options such as number of colours, dither etc.
EDIT: I have created a "proof of concept" script, it still needs further testing.
Copy link to clipboard
Copied
Save for Web (Legacy) already has an option to limit via a target file size, however, I'm not sure if it works with animated GIFs or adjusting the dimensions. This might need to be scripted.
Also, as a (Legacy) feature, I wouldn't hold my breath on seeing any new features being added to this old code.
Copy link to clipboard
Copied
Looks like the Optimise to File Size... feature does work for animated GIF files, however, it has a max limit of 2048K and it doesn't resize the px dimensions:
A custom script would be required to reduce dimensions to achieve a target size on drive, while retaining your other GIF options such as number of colours, dither etc.
EDIT: I have created a "proof of concept" script, it still needs further testing.
Copy link to clipboard
Copied
Here is the script that I mentioned. Select a target file size between 1-5 MB in half increments and canvas resize in 5% or 10% increments:
/*
Export GIF to Target File Size.jsx
Stephen Marsh
v1.0 - 14th June 2025
Info: Save the active document as GIF, scaling the canvas size down in 10% increments until the file size on drive is under specified limit
https://community.adobe.com/t5/photoshop-ecosystem-discussions/save-for-web-needs-a-file-size-limiter-tool-that-adjusts-the-dimensions-of-a-gif/td-p/15369326
*/
// Global variables
var theDirPath = "~/Desktop"; // Default directory path
var theDocName = app.activeDocument.name.replace(/\.[^\.]+$/, '');
var theFilePath;
var theScale = 100;
var minScale = 20; // Don't go below 20% scale
var scaleIncrement = 10; // Default scale reduction increment
var maxSize = 5 * 1024 * 1024; // Default 5MB in bytes
var colTableLength = 256; // Default color table length
var colorReductionAlgorithm = stringIDToTypeID("adaptive"); // Adaptive - stringIDToTypeID("adaptive") || Perceptual - charIDToTypeID( "Prcp" )
// Create UI
function createUI() {
var dialog = new Window("dialog", "Export GIF to Target File Size (v1.0)");
dialog.orientation = "column";
dialog.alignChildren = "fill";
dialog.spacing = 10;
dialog.margins = 16;
// Main panel
var mainPanel = dialog.add("panel", undefined, "Export Settings");
mainPanel.orientation = "column";
mainPanel.alignChildren = "fill";
mainPanel.spacing = 10;
mainPanel.margins = 16;
// Directory selection group
var dirGroup = mainPanel.add("group");
dirGroup.orientation = "row";
dirGroup.alignChildren = "center";
dirGroup.spacing = 10;
dirGroup.add("statictext", undefined, "Output Directory:");
var dirDisplay = dirGroup.add("edittext", undefined, theDirPath);
dirDisplay.preferredSize.width = 300;
dirDisplay.enabled = false;
var browseButton = dirGroup.add("button", undefined, "Browse...");
browseButton.preferredSize.width = 80;
// File size dropdown group
var sizeGroup = mainPanel.add("group");
sizeGroup.orientation = "row";
sizeGroup.alignChildren = "center";
sizeGroup.spacing = 10;
sizeGroup.add("statictext", undefined, "Maximum File Size (MB):");
var sizeArray = [1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5];
var sizeDropdown = sizeGroup.add("dropdownlist", undefined, sizeArray);
sizeDropdown.selection = 8; // Default to 5MB (index 8)
sizeDropdown.preferredSize.width = 80;
// Scale increment dropdown group
var scaleGroup = mainPanel.add("group");
scaleGroup.orientation = "row";
scaleGroup.alignChildren = "center";
scaleGroup.spacing = 10;
scaleGroup.add("statictext", undefined, "Canvas Scale Reduction:");
var scaleArray = ["5%", "10%"];
var scaleDropdown = scaleGroup.add("dropdownlist", undefined, scaleArray);
scaleDropdown.selection = 1; // Default to 10% (index 1)
scaleDropdown.preferredSize.width = 80;
// Button group
var buttonGroup = dialog.add("group");
buttonGroup.orientation = "row";
buttonGroup.alignment = "right";
buttonGroup.spacing = 10;
var cancelButton = buttonGroup.add("button", undefined, "Cancel");
var okButton = buttonGroup.add("button", undefined, "OK");
// Event handlers
browseButton.onClick = function () {
var folder = Folder.selectDialog("Select Output Directory", theDirPath);
if (folder) {
theDirPath = folder.fsName;
dirDisplay.text = theDirPath;
}
};
cancelButton.onClick = function () {
dialog.close(0);
};
okButton.onClick = function () {
if (sizeDropdown.selection && scaleDropdown.selection) {
maxSize = sizeDropdown.selection.text * 1024 * 1024; // Convert MB to bytes
scaleIncrement = parseInt(scaleDropdown.selection.text); // Extract number from "5%" or "10%"
dialog.close(1);
} else {
alert("Please select both maximum file size and scale reduction step.");
}
};
return dialog;
}
(function () {
try {
// Check if there's an active document
if (!app.documents.length) {
alert("No active document found. Please open a document first.");
return;
}
// Show UI dialog
var dialog = createUI();
var result = dialog.show();
// Exit if user cancelled
if (result === 0) {
return;
}
// Set the file path
theFilePath = theDirPath + "/" + theDocName + ".gif";
var fileObj = new File(theFilePath);
// Calculate maximum attempts
var maxAttempts = Math.floor((100 - minScale) / scaleIncrement) + 1;
var currentAttempt = 1;
// Create progress window
var progressWindow = createProgressWindow();
try {
// Loop: Save and check file size, reduce scale if needed
while (true) {
// Check if user cancelled
if (progressWindow.isCancelled()) {
return;
}
// Update progress
progressWindow.updateProgress(theScale, null, currentAttempt, maxAttempts);
saveForWebGIF(theDirPath, theFilePath, theScale, theScale);
// Wait to ensure that the file is written before checking its size
$.sleep(500);
fileObj = new File(theFilePath);
if (fileObj.exists) {
fileObj.open("r");
var fileSize = fileObj.length;
fileObj.close();
// Update progress with file size
progressWindow.updateProgress(theScale, fileSize, currentAttempt, maxAttempts);
if (fileSize <= maxSize) {
progressWindow.close();
alert("GIF saved under " + (maxSize / 1024 / 1024) + "MB at " + theScale + "% scale. File size: " + (fileSize / 1024).toFixed(2) + " KB");
break;
} else {
theScale -= scaleIncrement;
currentAttempt++;
if (theScale < minScale) {
progressWindow.close();
alert("Could not reduce file under " + (maxSize / 1024 / 1024) + "MB even at minimum (" + minScale + "% scale). File size: " + (fileSize / 1024).toFixed(2) + " KB");
break;
}
}
} else {
progressWindow.close();
alert("Failed to save GIF file.");
break;
}
}
} catch (e) {
progressWindow.close();
throw e;
}
} catch (e) {
alert("Error: " + e.message);
}
})();
///// Functions /////
function saveForWebGIF(outputDirPath, outputDirFilePath, hScale, vScale) {
var descriptor = new ActionDescriptor();
var descriptor2 = new ActionDescriptor();
var descriptor3 = new ActionDescriptor();
var descriptor4 = new ActionDescriptor();
var descriptor5 = new ActionDescriptor();
var descriptor6 = new ActionDescriptor();
var descriptor7 = new ActionDescriptor();
var descriptor8 = new ActionDescriptor();
var descriptor9 = new ActionDescriptor();
var descriptor10 = new ActionDescriptor();
var descriptor11 = new ActionDescriptor();
var descriptor12 = new ActionDescriptor();
var descriptor13 = new ActionDescriptor();
var descriptor14 = new ActionDescriptor();
var descriptor15 = new ActionDescriptor();
var descriptor16 = new ActionDescriptor();
var descriptor17 = new ActionDescriptor();
var list = new ActionList();
var list2 = new ActionList();
descriptor2.putEnumerated(charIDToTypeID("Op "), charIDToTypeID("SWOp"), charIDToTypeID("OpSa"));
descriptor2.putBoolean(charIDToTypeID("DIDr"), true);
descriptor2.putPath(stringIDToTypeID("in"), new File(outputDirPath));
descriptor2.putString(stringIDToTypeID("pathName"), outputDirFilePath);
descriptor2.putEnumerated(stringIDToTypeID("format"), charIDToTypeID("IRFm"), charIDToTypeID("GIFf"));
descriptor2.putBoolean(stringIDToTypeID("interfaceIconFrameDimmed"), false);
descriptor2.putEnumerated(charIDToTypeID("RedA"), charIDToTypeID("IRRd"), colorReductionAlgorithm); // Adaptive or Perceptual
descriptor2.putBoolean(charIDToTypeID("RChT"), false);
descriptor2.putBoolean(charIDToTypeID("RChV"), false);
descriptor2.putBoolean(charIDToTypeID("AuRd"), false);
descriptor2.putInteger(charIDToTypeID("NCol"), colTableLength); // Number of colors in the color table
descriptor2.putInteger(charIDToTypeID("DChS"), 0);
descriptor2.putInteger(charIDToTypeID("DCUI"), 0);
descriptor2.putBoolean(charIDToTypeID("DChT"), false);
descriptor2.putBoolean(charIDToTypeID("DChV"), false);
descriptor2.putInteger(charIDToTypeID("WebS"), 0);
descriptor2.putEnumerated(charIDToTypeID("TDth"), charIDToTypeID("IRDt"), stringIDToTypeID("none"));
descriptor2.putInteger(charIDToTypeID("TDtA"), 100);
descriptor2.putInteger(charIDToTypeID("Loss"), 0);
descriptor2.putInteger(charIDToTypeID("LChS"), 0);
descriptor2.putInteger(charIDToTypeID("LCUI"), 100);
descriptor2.putBoolean(charIDToTypeID("LChT"), false);
descriptor2.putBoolean(charIDToTypeID("LChV"), false);
descriptor2.putBoolean(stringIDToTypeID("transparency"), true);
descriptor2.putBoolean(charIDToTypeID("Mtt "), true);
descriptor2.putEnumerated(stringIDToTypeID("dither"), charIDToTypeID("IRDt"), stringIDToTypeID("diffusion"));
descriptor2.putInteger(stringIDToTypeID("ditherAmount"), 100);
descriptor2.putInteger(charIDToTypeID("MttR"), 255);
descriptor2.putInteger(charIDToTypeID("MttG"), 255);
descriptor2.putInteger(charIDToTypeID("MttB"), 255);
descriptor2.putUnitDouble(charIDToTypeID("HScl"), stringIDToTypeID("percentUnit"), hScale);
descriptor2.putUnitDouble(charIDToTypeID("VScl"), stringIDToTypeID("percentUnit"), vScale);
descriptor2.putBoolean(charIDToTypeID("SHTM"), false);
descriptor2.putBoolean(charIDToTypeID("SImg"), true);
descriptor2.putEnumerated(charIDToTypeID("SWsl"), charIDToTypeID("STsl"), charIDToTypeID("SLAl"));
descriptor2.putEnumerated(charIDToTypeID("SWch"), charIDToTypeID("STch"), charIDToTypeID("CHsR"));
descriptor2.putEnumerated(charIDToTypeID("SWmd"), charIDToTypeID("STmd"), charIDToTypeID("MDNn")); // "MDNn" for no metadata | "MDAl" for all metadata
descriptor2.putBoolean(charIDToTypeID("ohXH"), false);
descriptor2.putBoolean(charIDToTypeID("ohIC"), true);
descriptor2.putBoolean(charIDToTypeID("ohAA"), true);
descriptor2.putBoolean(charIDToTypeID("ohQA"), true);
descriptor2.putBoolean(charIDToTypeID("ohCA"), false);
descriptor2.putBoolean(charIDToTypeID("ohIZ"), true);
descriptor2.putEnumerated(charIDToTypeID("ohTC"), charIDToTypeID("SToc"), charIDToTypeID("OC03"));
descriptor2.putEnumerated(charIDToTypeID("ohAC"), charIDToTypeID("SToc"), charIDToTypeID("OC03"));
descriptor2.putInteger(charIDToTypeID("ohIn"), -1);
descriptor2.putEnumerated(charIDToTypeID("ohLE"), charIDToTypeID("STle"), charIDToTypeID("LE03"));
descriptor2.putEnumerated(charIDToTypeID("ohEn"), charIDToTypeID("STen"), charIDToTypeID("EN00"));
descriptor2.putBoolean(charIDToTypeID("olCS"), false);
descriptor2.putEnumerated(charIDToTypeID("olEC"), charIDToTypeID("STst"), charIDToTypeID("ST00"));
descriptor2.putEnumerated(charIDToTypeID("olWH"), charIDToTypeID("STwh"), charIDToTypeID("WH01"));
descriptor2.putEnumerated(charIDToTypeID("olSV"), charIDToTypeID("STsp"), charIDToTypeID("SP04"));
descriptor2.putEnumerated(charIDToTypeID("olSH"), charIDToTypeID("STsp"), charIDToTypeID("SP04"));
descriptor3.putEnumerated(charIDToTypeID("ncTp"), charIDToTypeID("STnc"), charIDToTypeID("NC00"));
list.putObject(charIDToTypeID("SCnc"), descriptor3);
descriptor4.putEnumerated(charIDToTypeID("ncTp"), charIDToTypeID("STnc"), charIDToTypeID("NC19"));
list.putObject(charIDToTypeID("SCnc"), descriptor4);
descriptor5.putEnumerated(charIDToTypeID("ncTp"), charIDToTypeID("STnc"), charIDToTypeID("NC28"));
list.putObject(charIDToTypeID("SCnc"), descriptor5);
descriptor6.putEnumerated(charIDToTypeID("ncTp"), charIDToTypeID("STnc"), charIDToTypeID("NC24"));
list.putObject(charIDToTypeID("SCnc"), descriptor6);
descriptor7.putEnumerated(charIDToTypeID("ncTp"), charIDToTypeID("STnc"), charIDToTypeID("NC24"));
list.putObject(charIDToTypeID("SCnc"), descriptor7);
descriptor8.putEnumerated(charIDToTypeID("ncTp"), charIDToTypeID("STnc"), charIDToTypeID("NC24"));
list.putObject(charIDToTypeID("SCnc"), descriptor8);
descriptor2.putList(charIDToTypeID("olNC"), list);
descriptor2.putBoolean(charIDToTypeID("obIA"), true);
descriptor2.putString(charIDToTypeID("obIP"), "");
descriptor2.putEnumerated(charIDToTypeID("obCS"), charIDToTypeID("STcs"), charIDToTypeID("CS01"));
descriptor9.putEnumerated(charIDToTypeID("ncTp"), charIDToTypeID("STnc"), charIDToTypeID("NC01"));
list2.putObject(charIDToTypeID("SCnc"), descriptor9);
descriptor10.putEnumerated(charIDToTypeID("ncTp"), charIDToTypeID("STnc"), charIDToTypeID("NC20"));
list2.putObject(charIDToTypeID("SCnc"), descriptor10);
descriptor11.putEnumerated(charIDToTypeID("ncTp"), charIDToTypeID("STnc"), charIDToTypeID("NC02"));
list2.putObject(charIDToTypeID("SCnc"), descriptor11);
descriptor12.putEnumerated(charIDToTypeID("ncTp"), charIDToTypeID("STnc"), charIDToTypeID("NC19"));
list2.putObject(charIDToTypeID("SCnc"), descriptor12);
descriptor13.putEnumerated(charIDToTypeID("ncTp"), charIDToTypeID("STnc"), charIDToTypeID("NC06"));
list2.putObject(charIDToTypeID("SCnc"), descriptor13);
descriptor14.putEnumerated(charIDToTypeID("ncTp"), charIDToTypeID("STnc"), charIDToTypeID("NC24"));
list2.putObject(charIDToTypeID("SCnc"), descriptor14);
descriptor15.putEnumerated(charIDToTypeID("ncTp"), charIDToTypeID("STnc"), charIDToTypeID("NC24"));
list2.putObject(charIDToTypeID("SCnc"), descriptor15);
descriptor16.putEnumerated(charIDToTypeID("ncTp"), charIDToTypeID("STnc"), charIDToTypeID("NC24"));
list2.putObject(charIDToTypeID("SCnc"), descriptor16);
descriptor17.putEnumerated(charIDToTypeID("ncTp"), charIDToTypeID("STnc"), charIDToTypeID("NC22"));
list2.putObject(charIDToTypeID("SCnc"), descriptor17);
descriptor2.putList(charIDToTypeID("ovNC"), list2);
descriptor2.putBoolean(charIDToTypeID("ovCM"), false);
descriptor2.putBoolean(charIDToTypeID("ovCW"), false);
descriptor2.putBoolean(charIDToTypeID("ovCU"), false);
descriptor2.putBoolean(charIDToTypeID("ovSF"), true);
descriptor2.putBoolean(charIDToTypeID("ovCB"), true);
descriptor2.putString(charIDToTypeID("ovSN"), "images");
descriptor.putObject(stringIDToTypeID("using"), stringIDToTypeID("SaveForWeb"), descriptor2);
executeAction(stringIDToTypeID("export"), descriptor, DialogModes.NO);
}
function createProgressWindow() {
var progressWindow = new Window("window", "Exporting GIF...");
progressWindow.orientation = "column";
progressWindow.alignChildren = "fill";
progressWindow.spacing = 10;
progressWindow.margins = 16;
progressWindow.preferredSize.width = 400;
// Status text
var statusText = progressWindow.add("statictext", undefined, "Initializing export...");
statusText.preferredSize.width = 380;
// Progress bar
var progressBar = progressWindow.add("progressbar", undefined, 0, 100);
progressBar.preferredSize.width = 380;
progressBar.preferredSize.height = 10;
// Scale and file size info
var scaleText = progressWindow.add("statictext", undefined, "Scale: 100%");
var sizeText = progressWindow.add("statictext", undefined, "File size: Calculating...");
// Cancel button
var buttonGroup = progressWindow.add("group");
buttonGroup.alignment = "center";
var cancelButton = buttonGroup.add("button", undefined, "Cancel");
var cancelled = false;
cancelButton.onClick = function () {
cancelled = true;
progressWindow.close();
};
progressWindow.show();
return {
window: progressWindow,
statusText: statusText,
progressBar: progressBar,
scaleText: scaleText,
sizeText: sizeText,
isCancelled: function () { return cancelled; },
updateProgress: function (scale, fileSize, attempt, maxAttempts) {
if (cancelled) return;
var progress = ((100 - scale + 10) / (100 - minScale + 10)) * 100;
this.progressBar.value = Math.min(progress, 100);
this.statusText.text = "Attempt " + attempt + " of " + maxAttempts + " - Testing scale " + scale + "%";
this.scaleText.text = "Scale: " + scale + "%";
if (fileSize) {
var sizeMB = (fileSize / 1024 / 1024).toFixed(2);
var targetMB = (maxSize / 1024 / 1024).toFixed(1);
this.sizeText.text = "File size: " + sizeMB + "MB (Target: <" + targetMB + "MB)";
}
this.window.update();
},
close: function () {
this.window.close();
}
};
}
Copy link to clipboard
Copied
Copy link to clipboard
Copied
I find this very amusing - welcome to my life in 1997 when I had to make animated banner ads that had to be less than 10k. You need to old school it to get those files sizes down - reduce the number of colors, the dithering makes the file size larger, etc. We didnt have fancy tools to help us back then. Sounds like Stephen has some better ideas.
Copy link to clipboard
Copied
Having gone through things the hard way with banner ads and restricitve specs back in the late 90's, you have a great grounding!
I'm guessing that in this case @Josh32367144i052 is just wanting to upload a long "animated gif silent video" which is why there is a 5mb limit and the pixel dimensions are irrelevant.
Find more inspiration, events, and resources on the new Adobe Community
Explore Now