tween24
Version:
Tween24.js is animation library that enables fast coding using method chains.
275 lines (248 loc) • 12.2 kB
text/typescript
import { Updater } from "./Updater";
import { ParamUpdater } from "./ParamUpdater";
import { HTMLUtil } from "../../utils/HTMLUtil";
import { StyleColorUpdater } from "./StyleColorUpdater";
import { ColorUtil } from "../../utils/ColorUtil";
import { MultiParamUpdater } from "./MultiParamUpdater";
import { StyleFilterUpdater } from "./StyleFilterUpdater";
export class StyleUpdater implements Updater {
static className:string = "StyleUpdater";
static readonly PARAM_REG:RegExp = new RegExp(/^[-0-9.]*/);
static readonly UNIT_REG :RegExp = new RegExp(/[^-0-9.].*/);
private _target :HTMLElement;
private _query :string|null;
private _param :{[key:string]:ParamUpdater|StyleColorUpdater};
private _key :string[];
private _unit :{[key:string]:string};
private _tweenKey :string[]|null;
private _clipUpdater :MultiParamUpdater|null;
private _filterUpdater:StyleFilterUpdater|null;
private _onceParam :{[key:string]:string}|null;
private _isUpdatedOnce:boolean;
private _pseudo :string|null;
private _style :CSSStyleDeclaration|undefined;
private _useWillChange: boolean;
constructor(target:HTMLElement, query:string|null) {
this._target = target;
this._query = query;
this._param ||= {};
this._key ||= [];
this._unit ||= {};
this._tweenKey = null;
this._clipUpdater = null;
this._filterUpdater = null;
this._onceParam = null;
this._isUpdatedOnce = false;
this._pseudo = this._query ? HTMLUtil.getPseudoQuery(this._query) : null;
this._useWillChange = false;
}
addPropStr(key:string, value:string, option:string|null = null) {
if (key.indexOf("clip::") > -1) {
if (key.indexOf("inset" ) > -1) this._clipUpdater ||= new MultiParamUpdater(this._target, "clip-path", "inset" , 4, false, "round", 4);
else if (key.indexOf("circle" ) > -1) this._clipUpdater ||= new MultiParamUpdater(this._target, "clip-path", "circle" , 1, false, "at" , 2);
else if (key.indexOf("ellipse") > -1) this._clipUpdater ||= new MultiParamUpdater(this._target, "clip-path", "ellipse", 2, false, "at" , 2);
const index = parseFloat(key.slice(-1));
if (key.indexOf("round") > -1 || key.indexOf("at") > -1) this._clipUpdater?.addPropStr2(index, value);
else this._clipUpdater?.addPropStr(index, value);
}
if (key.indexOf("filter::") > -1) {
this._filterUpdater ||= new StyleFilterUpdater(this._target);
this._filterUpdater.addPropStr(key.split("::")[1], value);
}
else {
const val :RegExpMatchArray|null = String(value).match(StyleUpdater.PARAM_REG);
const unit:RegExpMatchArray|null = String(value).match(StyleUpdater.UNIT_REG);
if (ColorUtil.isColorCode(value)) {
this._param[key] = new StyleColorUpdater(key, value);
this._key.push(key);
}
// 数値を持っているスタイルの場合
else if (val && val[0]?.length) {
const original = HTMLUtil.getComputedStyle(this._target).getPropertyValue(key);
const originalUnit = original.match(StyleUpdater.UNIT_REG);
let targetUnit;
let targetValue;
if (unit && originalUnit && originalUnit[0].length && unit[0] != originalUnit[0]) {
this._target.style.setProperty(key, value);
targetValue = HTMLUtil.getComputedStyle(this._target).getPropertyValue(key);
this._target.style.setProperty(key, original);
targetUnit = targetValue.match(StyleUpdater.UNIT_REG);
}
else {
targetValue = value;
targetUnit = value.match(StyleUpdater.UNIT_REG);
}
let updater:ParamUpdater;
// 相対値の場合
if (option) {
updater = new ParamUpdater(key, parseFloat(original), value);
switch (option) {
case ParamUpdater.RELATIVE_AT_SETTING :
updater.set$value(parseFloat(value));
break;
case ParamUpdater.RELATIVE_AT_RUNNING :
updater.set$$value(parseFloat(value));
break;
}
}
// 絶対値の場合
else {
updater = new ParamUpdater(key, parseFloat(targetValue), value);
}
this._param[key] = updater;
this._unit [key] = targetUnit ? targetUnit[0] : "";
this._key.push(key);
}
// 文字列を設定するスタイルの場合
else {
this._onceParam ||= {};
this._onceParam[key] = value;
}
}
}
init(useWillChange:boolean) {
this._useWillChange = useWillChange;
if (!this._style && this._pseudo && this._query) {
this._style = HTMLUtil.getAddedStyleByElement(this._target, this._pseudo);
}
this._isUpdatedOnce = false;
this._tweenKey = this._key.concat();
for (const key of this._tweenKey) {
const value:string = HTMLUtil.getComputedStyle(this._target, this._pseudo).getPropertyValue(key);
if (ColorUtil.isColorCode(value)) {
(this._param[key] as StyleColorUpdater).init(value);
this._unit[key] ||= "";
}
else {
const val :RegExpMatchArray|null = value.match(StyleUpdater.PARAM_REG);
const unit:RegExpMatchArray|null = value.match(StyleUpdater.UNIT_REG);
this._unit[key] ||= unit && val && val[0].length ? unit[0] : "";
(this._param[key] as ParamUpdater).init(Number(val ? val : 0));
}
}
if (this._useWillChange) {
HTMLUtil.addWillChange(this._style || this._target.style, this._key);
}
if (this._clipUpdater) {
this._clipUpdater.init(this._target.style.clipPath);
if (this._useWillChange) {
HTMLUtil.addWillChange(this._style || this._target.style, [this._clipUpdater.key]);
}
}
if (this._filterUpdater) {
this._filterUpdater.init(getComputedStyle(this._target).filter);
if (this._useWillChange) {
HTMLUtil.addWillChange(this._style || this._target.style, [this._filterUpdater.key]);
}
}
}
update(progress:number) {
if (!this._isUpdatedOnce) {
for (const key in this._onceParam) {
const value:string = this._onceParam[key];
if (this._style) this._style.setProperty(key, value);
else HTMLUtil.setStyleProp(this._target, key, value);
}
this._isUpdatedOnce = true;
}
if (this._tweenKey) {
for (const key of this._tweenKey) {
const value:string = this._param[key].update(progress) + this._unit[key];
if (this._style) this._style.setProperty(key, value);
else HTMLUtil.setStyleProp(this._target, key, value);
}
}
if (this._clipUpdater) {
const value:string = this._clipUpdater.update(progress);
if (this._style) this._style.setProperty(this._clipUpdater.key, value);
else HTMLUtil.setStyleProp(this._target, this._clipUpdater.key, value);
}
if (this._filterUpdater) {
const value:string = this._filterUpdater.update(progress);
if (this._style) this._style.setProperty(this._filterUpdater.key, value);
else HTMLUtil.setStyleProp(this._target, this._filterUpdater.key, value);
}
if (progress == 1) this.complete();
}
overwrite(updater:StyleUpdater):void {
if (this._target == updater._target) {
const targetKey:string[]|null = updater._tweenKey;
if (this._tweenKey && targetKey) {
for (const key of targetKey) {
const i = this._tweenKey.indexOf(key);
if (i > -1) this._tweenKey.splice(i, 1);
}
}
if (this._clipUpdater && updater._clipUpdater) {
this._clipUpdater.overwrite(updater._clipUpdater);
}
if (this._filterUpdater && updater._filterUpdater) {
this._filterUpdater.overwrite(updater._filterUpdater);
}
}
}
getMaxAbsDelta():number {
const deltas:number[] = [];
if (this._param) {
for (const key in this._param)
deltas.push(Math.abs(this._param[key].getDelta()));
}
if (this._clipUpdater)
deltas.push(this._clipUpdater.getDelta());
if (this._filterUpdater)
deltas.push(this._filterUpdater.getDelta());
return Math.max(...deltas);
}
clone(target:any = this._target, query:string|null = this._query):StyleUpdater {
const copy:StyleUpdater = new StyleUpdater(target, query);
if (this._param) {
copy._param = {};
for (const key in this._param) {
const value = this._param[key].originalValue;
const unit = String(value).match(StyleUpdater.UNIT_REG);
let targetValue = NaN;
if (unit && unit[0] == "%") {
const original = target.style.getPropertyValue(key);
target.style.setProperty(key, value);
targetValue = parseFloat(HTMLUtil.getComputedStyle(target).getPropertyValue(key));
target.style.setProperty(key, original);
}
copy._param[key] = this._param[key].clone(targetValue);
}
}
if (this._key ) copy._key = [ ...this._key ];
if (this._unit ) copy._unit = { ...this._unit };
if (this._onceParam ) copy._onceParam = { ...this._onceParam };
if (this._clipUpdater ) copy._clipUpdater = this._clipUpdater .clone(target);
if (this._filterUpdater) copy._filterUpdater = this._filterUpdater.clone(target);
return copy;
}
toString():string {
let str:string = "";
if (this._param && this._key)
for (const key of this._key)
str += this._param[key]?.toString() + (this._unit[key]?.toString() || "") + " ";
if (this._clipUpdater)
str += this._clipUpdater.key + ":" + this._clipUpdater.toString() + " ";
if (this._filterUpdater)
str += this._filterUpdater.key + ":" + this._filterUpdater.toString() + " ";
for (const key in this._onceParam)
str += key + ":" + this._onceParam[key].toString() + " ";
return str.trim();
}
addProp(key:string, value:number) {
}
setBezier(bezierX:number, bezierY:number) {
}
complete() {
if (this._useWillChange) {
HTMLUtil.removeWillChange(this._style || this._target.style, this._key);
if (this._clipUpdater) {
HTMLUtil.removeWillChange(this._style || this._target.style, [this._clipUpdater.key]);
}
if (this._filterUpdater) {
HTMLUtil.removeWillChange(this._style || this._target.style, [this._filterUpdater.key]);
}
}
}
}