Файлы Lottie представляют собой JSON-описание анимации, экспортированной из After Effects через Bodymovin. Несмотря на формальную структуру, спецификация допускает вариативность, а некоторые экспортеры создают несовместимые или частично некорректные файлы.
Базовая структура Lottie-документа включает ключевые секции:
v — версия форматаfr — frame rateip, op — начало и конец анимацииlayers — слои композицииassets — внешние ресурсы (изображения, precomps)w, h — размеры сценыКаждый из этих узлов является точкой обязательной проверки перед передачей данных в Lottie Web, поскольку ошибка в любом из них приводит к некорректному рендерингу или полной остановке анимации.
Первый уровень валидации связан с синтаксисом и типами данных.
function isObject(value) {
return value !== null && typeof value === "object" && !Array.isArray(value);
}
function validateBasicStructure(data) {
if (!isObject(data)) return false;
const requiredFields = ["v", "fr", "ip", "op", "layers"];
for (const field of requiredFields) {
if (!(field in data)) return false;
}
return true;
}
На этом этапе проверяется только минимальная целостность документа. Однако Lottie допускает вложенные структуры, поэтому этого уровня недостаточно для production-использования.
Следующий слой валидации связан с числовыми ограничениями и типами данных.
Критические поля:
fr (frame rate) — положительное числоip, op — целые числа, где
op > ipw, h — положительные размерыlayers[] — массив объектовfunction validateRanges(data) {
if (typeof data.fr !== "number" || data.fr <= 0) return false;
if (typeof data.ip !== "number") return false;
if (typeof data.op !== "number") return false;
if (data.op <= data.ip) return false;
if (data.w && data.w <= 0) return false;
if (data.h && data.h <= 0) return false;
return Array.isArray(data.layers);
}
Ошибки в этих значениях приводят к невозможности корректного расчета временной шкалы.
Слои являются ядром Lottie-анимации. Каждый слой имеет обязательные поля:
ty — тип слояind — индекс слояks — трансформацииshapes или refId (в зависимости от
типа)Типы слоёв включают:
function validateLayer(layer) {
if (typeof layer.ty !== "number") return false;
if (typeof layer.ind !== "number") return false;
if (!isObject(layer.ks)) return false;
const validTypes = [0, 1, 2, 4, 5];
if (!validTypes.includes(layer.ty)) return false;
return true;
}
function validateLayers(layers) {
return layers.every(validateLayer);
}
Особое внимание требуется слоям типа shape (ty: 4), так
как они содержат вложенные структуры shapes[], где часто
возникают ошибки экспорта.
Shape-структуры представляют собой дерево операторов:
gr — группыel — эллипсыrc — прямоугольникиsh — путиfl, st — заливки и обводкиОшибки в этой структуре часто связаны с отсутствием обязательных параметров анимации.
function validateShape(shape) {
if (!shape || typeof shape.ty !== "string") return false;
const allowed = ["gr", "el", "rc", "sh", "fl", "st"];
if (!allowed.includes(shape.ty)) return false;
return true;
}
function validateShapes(shapes) {
if (!Array.isArray(shapes)) return false;
return shapes.every(validateShape);
}
Раздел assets содержит изображения и
precomposition-данные. Ошибки здесь приводят к отсутствию текстур или
падению рендера.
Ключевые поля:
idw, hu (base path)p (file name)e (embedded flag)function validateAsset(asset) {
if (!asset.id) return false;
if (asset.p && typeof asset.p !== "string") return false;
if (asset.u && typeof asset.u !== "string") return false;
return true;
}
function validateAssets(assets) {
if (!Array.isArray(assets)) return false;
return assets.every(validateAsset);
}
Для промышленной проверки используется формальная схема. Наиболее распространённый инструмент — AJV.
import Ajv from "ajv";
const ajv = new Ajv({ strict: false });
const lottieSchema = {
type: "object",
required: ["v", "fr", "ip", "op", "layers"],
properties: {
v: { type: "string" },
fr: { type: "number", exclusiveMinimum: 0 },
ip: { type: "number" },
op: { type: "number" },
w: { type: "number" },
h: { type: "number" },
layers: {
type: "array",
items: { type: "object" }
},
assets: {
type: "array",
items: { type: "object" }
}
}
};
const validate = ajv.compile(lottieSchema);
function validateWithSchema(json) {
return validate(json);
}
Схемная проверка обеспечивает структурную корректность, но не гарантирует семантическую валидность анимации.
Lottie-файлы часто загружаются из внешних источников, что создаёт риск:
__proto__,
constructorshapes и
keyframesassetsfunction sanitizeKeys(obj) {
if (!isObject(obj)) return obj;
const forbidden = ["__proto__", "constructor", "prototype"];
for (const key of Object.keys(obj)) {
if (forbidden.includes(key)) {
delete obj[key];
continue;
}
obj[key] = sanitizeKeys(obj[key]);
}
return obj;
}
Для предотвращения деградации производительности вводятся ограничения:
const LIMITS = {
layers: 200,
shapesPerLayer: 300,
keyframesPerProperty: 500
};
function validateComplexity(data) {
if (data.layers.length > LIMITS.layers) return false;
for (const layer of data.layers) {
if (layer.shapes && layer.shapes.length > LIMITS.shapesPerLayer) {
return false;
}
}
return true;
}
Keyframes определяют поведение параметров во времени. Ошибки в них приводят к скачкам или разрыву анимации.
Проверяем:
t (time)s (start value)e (end value)function validateKeyframe(kf) {
if (typeof kf.t !== "number") return false;
if (!("s" in kf)) return false;
if (kf.t < 0) return false;
return true;
}
Полноценная проверка Lottie JSON строится как последовательность этапов:
function validateLottie(json) {
if (!validateBasicStructure(json)) return false;
if (!validateRanges(json)) return false;
if (!validateLayers(json.layers)) return false;
if (!validateAssets(json.assets || [])) return false;
if (!validateComplexity(json)) return false;
sanitizeKeys(json);
return true;
}
Перед передачей данных в рендерер используется защитная прослойка.
import lottie from "lottie-web";
async function loadAnimation(url, container) {
const res = await fetch(url);
const json = await res.json();
if (!validateLottie(json)) {
throw new Error("Invalid Lottie file");
}
return lottie.loadAnimation({
container,
renderer: "svg",
loop: true,
autoplay: true,
animationData: json
});
}
При работе с большими файлами валидация выносится в отдельный поток, чтобы не блокировать UI.
// worker.js
self.onmess age = function (e) {
const json = e.data;
const result = validateLottie(json);
self.postMessage({ result });
};
Основной поток получает только результат проверки и принимает решение о загрузке.
Помимо структурных ошибок встречаются логические:
op меньше максимального keyframe timeindfunction validateLogic(data) {
const indices = new Set();
for (const layer of data.layers) {
if (indices.has(layer.ind)) return false;
indices.add(layer.ind);
}
return true;
}