[{"data":1,"prerenderedAt":1281},["ShallowReactive",2],{"docs:\u002Fdocs\u002Fjavascript":3},{"id":4,"title":5,"body":6,"description":1273,"extension":1274,"meta":1275,"navigation":72,"path":1276,"rawbody":1277,"seo":1278,"stem":1279,"__hash__":1280},"docs\u002Fdocs\u002Fjavascript.md","JavaScript",{"type":7,"value":8,"toc":1263},"minimark",[9,13,29,33,38,183,197,206,210,223,308,317,321,330,417,421,462,489,499,503,506,640,651,772,775,832,835,838,958,974,978,1035,1052,1056,1062,1066,1078,1169,1173,1252,1259],[10,11,12],"p",{},"Every CSS property has a JavaScript prop. Use JavaScript when motion depends on application state, or when CSS can't describe it.",[10,14,15,16,20,21,23,24,28],{},"Every example on this site comes both ways. Open its code and switch ",[17,18,19],"strong",{},"Motion in"," to ",[17,22,5],{}," to see it written with ",[25,26,27],"code",{},"kinesis()",".",[30,31,27],"h2",{"id":32},"kinesis",[10,34,35,37],{},[25,36,27],{}," takes an element, a selector, or a list of elements, plus props. Props are the CSS names in camelCase. Lengths are numbers in px, angles in degrees, times in ms.",[39,40,45],"pre",{"className":41,"code":42,"language":43,"meta":44,"style":44},"language-js shiki shiki-themes github-dark-default","import { kinesis } from \"@amineyarman\u002Fkinesis\"\n\nkinesis(\".card\", { tilt: 8, area: \"self\", motion: \"soft\" })\n\nkinesis(document.querySelector(\".cta\"), {\n  magnetic: 0.35,\n  radius: 160,\n  when: \"press\",\n  scale: 0.94,\n})\n","js","",[25,46,47,67,74,108,113,132,144,155,166,177],{"__ignoreMap":44},[48,49,52,56,60,63],"span",{"class":50,"line":51},"line",1,[48,53,55],{"class":54},"suJrU","import",[48,57,59],{"class":58},"sZEs4"," { kinesis } ",[48,61,62],{"class":54},"from",[48,64,66],{"class":65},"s9uIt"," \"@amineyarman\u002Fkinesis\"\n",[48,68,70],{"class":50,"line":69},2,[48,71,73],{"emptyLinePlaceholder":72},true,"\n",[48,75,77,80,83,86,89,93,96,99,102,105],{"class":50,"line":76},3,[48,78,32],{"class":79},"sc3cj",[48,81,82],{"class":58},"(",[48,84,85],{"class":65},"\".card\"",[48,87,88],{"class":58},", { tilt: ",[48,90,92],{"class":91},"sFSAA","8",[48,94,95],{"class":58},", area: ",[48,97,98],{"class":65},"\"self\"",[48,100,101],{"class":58},", motion: ",[48,103,104],{"class":65},"\"soft\"",[48,106,107],{"class":58}," })\n",[48,109,111],{"class":50,"line":110},4,[48,112,73],{"emptyLinePlaceholder":72},[48,114,116,118,121,124,126,129],{"class":50,"line":115},5,[48,117,32],{"class":79},[48,119,120],{"class":58},"(document.",[48,122,123],{"class":79},"querySelector",[48,125,82],{"class":58},[48,127,128],{"class":65},"\".cta\"",[48,130,131],{"class":58},"), {\n",[48,133,135,138,141],{"class":50,"line":134},6,[48,136,137],{"class":58},"  magnetic: ",[48,139,140],{"class":91},"0.35",[48,142,143],{"class":58},",\n",[48,145,147,150,153],{"class":50,"line":146},7,[48,148,149],{"class":58},"  radius: ",[48,151,152],{"class":91},"160",[48,154,143],{"class":58},[48,156,158,161,164],{"class":50,"line":157},8,[48,159,160],{"class":58},"  when: ",[48,162,163],{"class":65},"\"press\"",[48,165,143],{"class":58},[48,167,169,172,175],{"class":50,"line":168},9,[48,170,171],{"class":58},"  scale: ",[48,173,174],{"class":91},"0.94",[48,176,143],{"class":58},[48,178,180],{"class":50,"line":179},10,[48,181,182],{"class":58},"})\n",[10,184,185,186,189,190,189,193,196],{},"Arrays are pairs: ",[25,187,188],{},"parallax: [30, 12]",", ",[25,191,192],{},"y: [24, 0]",[25,194,195],{},"color: [\"#999\", \"tomato\"]",". A single value means \"from rest to this\", as in CSS.",[10,198,199,201,202,205],{},[25,200,27],{}," works on any element, inside a ",[25,203,204],{},"[data-kinesis]"," scope or not. Outside a scope, the pointer area is the viewport.",[30,207,209],{"id":208},"set-and-reset","set() and reset()",[10,211,212,214,215,218,219,222],{},[25,213,27],{}," returns a handle. ",[25,216,217],{},"set()"," changes props, which override CSS for that element. ",[25,220,221],{},"reset()"," hands them back to CSS.",[39,224,226],{"className":41,"code":225,"language":43,"meta":44,"style":44},"const card = kinesis(document.querySelector(\".card\"))\n\ncard.set({ tilt: 14 })   \u002F\u002F JavaScript owns tilt now\ncard.reset(\"tilt\")       \u002F\u002F back to whatever the stylesheet says\ncard.reset()             \u002F\u002F back to CSS for everything\n",[25,227,228,253,257,278,296],{"__ignoreMap":44},[48,229,230,233,236,239,242,244,246,248,250],{"class":50,"line":51},[48,231,232],{"class":54},"const",[48,234,235],{"class":91}," card",[48,237,238],{"class":54}," =",[48,240,241],{"class":79}," kinesis",[48,243,120],{"class":58},[48,245,123],{"class":79},[48,247,82],{"class":58},[48,249,85],{"class":65},[48,251,252],{"class":58},"))\n",[48,254,255],{"class":50,"line":69},[48,256,73],{"emptyLinePlaceholder":72},[48,258,259,262,265,268,271,274],{"class":50,"line":76},[48,260,261],{"class":58},"card.",[48,263,264],{"class":79},"set",[48,266,267],{"class":58},"({ tilt: ",[48,269,270],{"class":91},"14",[48,272,273],{"class":58}," })   ",[48,275,277],{"class":276},"sH3jZ","\u002F\u002F JavaScript owns tilt now\n",[48,279,280,282,285,287,290,293],{"class":50,"line":110},[48,281,261],{"class":58},[48,283,284],{"class":79},"reset",[48,286,82],{"class":58},[48,288,289],{"class":65},"\"tilt\"",[48,291,292],{"class":58},")       ",[48,294,295],{"class":276},"\u002F\u002F back to whatever the stylesheet says\n",[48,297,298,300,302,305],{"class":50,"line":115},[48,299,261],{"class":58},[48,301,284],{"class":79},[48,303,304],{"class":58},"()             ",[48,306,307],{"class":276},"\u002F\u002F back to CSS for everything\n",[10,309,310,312,313,316],{},[25,311,217],{}," doesn't touch the DOM or re-read styles, so it's cheap enough for every input event or animation frame. Prefer it to inline ",[25,314,315],{},"--k-*"," styles for state-driven motion.",[30,318,320],{"id":319},"bind","bind()",[10,322,323,325,326,329],{},[25,324,320],{}," drives an output from a ",[17,327,328],{},"signal",", a number that changes over time. Bound values add to CSS effects. Scale and opacity multiply.",[39,331,333],{"className":41,"code":332,"language":43,"meta":44,"style":44},"import { kinesis, pointer } from \"@amineyarman\u002Fkinesis\"\n\nkinesis(\".needle\").bind({\n  rotate: pointer.speed.map([0, 4000], [-90, 90]).spring(\"bouncy\"),\n})\n",[25,334,335,346,350,367,413],{"__ignoreMap":44},[48,336,337,339,342,344],{"class":50,"line":51},[48,338,55],{"class":54},[48,340,341],{"class":58}," { kinesis, pointer } ",[48,343,62],{"class":54},[48,345,66],{"class":65},[48,347,348],{"class":50,"line":69},[48,349,73],{"emptyLinePlaceholder":72},[48,351,352,354,356,359,362,364],{"class":50,"line":76},[48,353,32],{"class":79},[48,355,82],{"class":58},[48,357,358],{"class":65},"\".needle\"",[48,360,361],{"class":58},").",[48,363,319],{"class":79},[48,365,366],{"class":58},"({\n",[48,368,369,372,375,378,381,383,386,389,392,395,397,399,402,405,407,410],{"class":50,"line":110},[48,370,371],{"class":58},"  rotate: pointer.speed.",[48,373,374],{"class":79},"map",[48,376,377],{"class":58},"([",[48,379,380],{"class":91},"0",[48,382,189],{"class":58},[48,384,385],{"class":91},"4000",[48,387,388],{"class":58},"], [",[48,390,391],{"class":54},"-",[48,393,394],{"class":91},"90",[48,396,189],{"class":58},[48,398,394],{"class":91},[48,400,401],{"class":58},"]).",[48,403,404],{"class":79},"spring",[48,406,82],{"class":58},[48,408,409],{"class":65},"\"bouncy\"",[48,411,412],{"class":58},"),\n",[48,414,415],{"class":50,"line":115},[48,416,182],{"class":58},[418,419],"demo",{"id":420},"velocity",[10,422,423,424,189,427,189,430,189,433,189,436,189,439,189,442,189,445,189,448,189,451,454,455,458,459,461],{},"Bindable channels: ",[25,425,426],{},"x",[25,428,429],{},"y",[25,431,432],{},"z",[25,434,435],{},"rotate",[25,437,438],{},"rotateX",[25,440,441],{},"rotateY",[25,443,444],{},"scale",[25,446,447],{},"opacity",[25,449,450],{},"blur",[25,452,453],{},"progress",", and any CSS custom property starting with ",[25,456,457],{},"--",". Binding ",[25,460,453],{}," drives the element's reaction outputs in place of its trigger.",[39,463,465],{"className":41,"code":464,"language":43,"meta":44,"style":44},"kinesis(\".meter\").bind({ \"--level\": audio.volume })\n",[25,466,467],{"__ignoreMap":44},[48,468,469,471,473,476,478,480,483,486],{"class":50,"line":51},[48,470,32],{"class":79},[48,472,82],{"class":58},[48,474,475],{"class":65},"\".meter\"",[48,477,361],{"class":58},[48,479,319],{"class":79},[48,481,482],{"class":58},"({ ",[48,484,485],{"class":65},"\"--level\"",[48,487,488],{"class":58},": audio.volume })\n",[10,490,491,494,495,498],{},[25,492,493],{},"unbind(\"rotate\")"," removes one binding, ",[25,496,497],{},"unbind()"," removes all of them.",[30,500,502],{"id":501},"signals","Signals",[10,504,505],{},"Signals update once per frame. Kinesis provides these:",[507,508,509,522],"table",{},[510,511,512],"thead",{},[513,514,515,519],"tr",{},[516,517,518],"th",{},"Signal",[516,520,521],{},"Value",[523,524,525,539,552,568,578,594,604,614,627],"tbody",{},[513,526,527,536],{},[528,529,530,189,533],"td",{},[25,531,532],{},"pointer.x",[25,534,535],{},"pointer.y",[528,537,538],{},"position in the viewport, px",[513,540,541,549],{},[528,542,543,189,546],{},[25,544,545],{},"pointer.nx",[25,547,548],{},"pointer.ny",[528,550,551],{},"position from -1 to 1 across the viewport",[513,553,554,565],{},[528,555,556,189,559,189,562],{},[25,557,558],{},"pointer.velocityX",[25,560,561],{},"pointer.velocityY",[25,563,564],{},"pointer.speed",[528,566,567],{},"px per second, smoothed",[513,569,570,575],{},[528,571,572],{},[25,573,574],{},"pointer.present",[528,576,577],{},"1 while a pointer is over the page",[513,579,580,591],{},[528,581,582,189,585,189,588],{},[25,583,584],{},"scroll.y",[25,586,587],{},"scroll.progress",[25,589,590],{},"scroll.velocity",[528,592,593],{},"document scroll",[513,595,596,601],{},[528,597,598],{},[25,599,600],{},"time",[528,602,603],{},"seconds since the page loaded",[513,605,606,611],{},[528,607,608],{},[25,609,610],{},"handle.progress",[528,612,613],{},"an element's reaction progress",[513,615,616,621],{},[528,617,618],{},[25,619,620],{},"handle.near",[528,622,623,624],{},"how close the pointer is to that element, 1 at its center to 0 at its ",[25,625,626],{},"radius",[513,628,629,637],{},[528,630,631,189,634],{},[25,632,633],{},"handle.pointer.x",[25,635,636],{},"handle.pointer.y",[528,638,639],{},"the pointer's offset from that element's resting center, px",[10,641,642,643,646,647,650],{},"Make your own with ",[25,644,645],{},"value()"," and combine signals with ",[25,648,649],{},"computed()",":",[39,652,654],{"className":41,"code":653,"language":43,"meta":44,"style":44},"import { computed, kinesis, value } from \"@amineyarman\u002Fkinesis\"\n\nconst volume = value(0)          \u002F\u002F set it from anywhere: volume.set(0.7)\nconst loud = computed(() => volume.get() * 2)\n\nkinesis(\".bars\").bind({ scale: loud.map([0, 1], [1, 1.5]) })\n",[25,655,656,667,671,693,729,733],{"__ignoreMap":44},[48,657,658,660,663,665],{"class":50,"line":51},[48,659,55],{"class":54},[48,661,662],{"class":58}," { computed, kinesis, value } ",[48,664,62],{"class":54},[48,666,66],{"class":65},[48,668,669],{"class":50,"line":69},[48,670,73],{"emptyLinePlaceholder":72},[48,672,673,675,678,680,683,685,687,690],{"class":50,"line":76},[48,674,232],{"class":54},[48,676,677],{"class":91}," volume",[48,679,238],{"class":54},[48,681,682],{"class":79}," value",[48,684,82],{"class":58},[48,686,380],{"class":91},[48,688,689],{"class":58},")          ",[48,691,692],{"class":276},"\u002F\u002F set it from anywhere: volume.set(0.7)\n",[48,694,695,697,700,702,705,708,711,714,717,720,723,726],{"class":50,"line":110},[48,696,232],{"class":54},[48,698,699],{"class":91}," loud",[48,701,238],{"class":54},[48,703,704],{"class":79}," computed",[48,706,707],{"class":58},"(() ",[48,709,710],{"class":54},"=>",[48,712,713],{"class":58}," volume.",[48,715,716],{"class":79},"get",[48,718,719],{"class":58},"() ",[48,721,722],{"class":54},"*",[48,724,725],{"class":91}," 2",[48,727,728],{"class":58},")\n",[48,730,731],{"class":50,"line":115},[48,732,73],{"emptyLinePlaceholder":72},[48,734,735,737,739,742,744,746,749,751,753,755,757,760,762,764,766,769],{"class":50,"line":134},[48,736,32],{"class":79},[48,738,82],{"class":58},[48,740,741],{"class":65},"\".bars\"",[48,743,361],{"class":58},[48,745,319],{"class":79},[48,747,748],{"class":58},"({ scale: loud.",[48,750,374],{"class":79},[48,752,377],{"class":58},[48,754,380],{"class":91},[48,756,189],{"class":58},[48,758,759],{"class":91},"1",[48,761,388],{"class":58},[48,763,759],{"class":91},[48,765,189],{"class":58},[48,767,768],{"class":91},"1.5",[48,770,771],{"class":58},"]) })\n",[10,773,774],{},"Every signal has:",[776,777,778,794,801,812,820],"ul",{},[779,780,781,786,787,790,791,28],"li",{},[17,782,783],{},[25,784,785],{},"map([inMin, inMax], [outMin, outMax])"," maps linearly, clamped to the output range. A third argument ",[25,788,789],{},"{ clamp: false }"," extrapolates. A function also works: ",[25,792,793],{},"map(v => v * v)",[779,795,796],{},[17,797,798],{},[25,799,800],{},"clamp(min, max)",[779,802,803,808,809,28],{},[17,804,805],{},[25,806,807],{},"spring(preset)"," follows the signal with a spring, from a preset name or ",[25,810,811],{},"{ stiffness, damping }",[779,813,814,819],{},[17,815,816],{},[25,817,818],{},"get()"," returns the current value.",[779,821,822,827,828,831],{},[17,823,824],{},[25,825,826],{},"subscribe(fn)"," calls ",[25,829,830],{},"fn"," with each new value, at most once per frame, and returns an unsubscribe function.",[10,833,834],{},"A signal is computed at most once per frame, however many elements read it. Signals nothing reads cost nothing.",[10,836,837],{},"Per-element signals keep custom motion short. Grass that bends behind a fast pointer, most strongly near it:",[39,839,841],{"className":41,"code":840,"language":43,"meta":44,"style":44},"for (const el of document.querySelectorAll(\".blade\")) {\n  const blade = kinesis(el, { radius: 220 })\n  blade.bind({\n    rotate: computed(() => (pointer.velocityX.get() \u002F 45) * blade.near.get()).spring(\"bouncy\"),\n  })\n}\n",[25,842,843,873,893,902,948,953],{"__ignoreMap":44},[48,844,845,848,851,853,856,859,862,865,867,870],{"class":50,"line":51},[48,846,847],{"class":54},"for",[48,849,850],{"class":58}," (",[48,852,232],{"class":54},[48,854,855],{"class":91}," el",[48,857,858],{"class":54}," of",[48,860,861],{"class":58}," document.",[48,863,864],{"class":79},"querySelectorAll",[48,866,82],{"class":58},[48,868,869],{"class":65},"\".blade\"",[48,871,872],{"class":58},")) {\n",[48,874,875,878,881,883,885,888,891],{"class":50,"line":69},[48,876,877],{"class":54},"  const",[48,879,880],{"class":91}," blade",[48,882,238],{"class":54},[48,884,241],{"class":79},[48,886,887],{"class":58},"(el, { radius: ",[48,889,890],{"class":91},"220",[48,892,107],{"class":58},[48,894,895,898,900],{"class":50,"line":76},[48,896,897],{"class":58},"  blade.",[48,899,319],{"class":79},[48,901,366],{"class":58},[48,903,904,907,910,912,914,917,919,921,924,927,930,932,935,937,940,942,944,946],{"class":50,"line":110},[48,905,906],{"class":58},"    rotate: ",[48,908,909],{"class":79},"computed",[48,911,707],{"class":58},[48,913,710],{"class":54},[48,915,916],{"class":58}," (pointer.velocityX.",[48,918,716],{"class":79},[48,920,719],{"class":58},[48,922,923],{"class":54},"\u002F",[48,925,926],{"class":91}," 45",[48,928,929],{"class":58},") ",[48,931,722],{"class":54},[48,933,934],{"class":58}," blade.near.",[48,936,716],{"class":79},[48,938,939],{"class":58},"()).",[48,941,404],{"class":79},[48,943,82],{"class":58},[48,945,409],{"class":65},[48,947,412],{"class":58},[48,949,950],{"class":50,"line":115},[48,951,952],{"class":58},"  })\n",[48,954,955],{"class":50,"line":134},[48,956,957],{"class":58},"}\n",[10,959,960,961,966,967,970,971,28],{},"The robot arm on the ",[962,963,965],"a",{"href":964},"\u002F#arm","home page"," aims two joints the same way, from ",[25,968,969],{},"shoulder.pointer.x"," and ",[25,972,973],{},"shoulder.pointer.y",[30,975,977],{"id":976},"events","Events",[39,979,981],{"className":41,"code":980,"language":43,"meta":44,"style":44},"kinesis(\".sheet\").on(\"dragend\", ({ x, y, velocityX, velocityY }) => { \u002F* … *\u002F })\n",[25,982,983],{"__ignoreMap":44},[48,984,985,987,989,992,994,997,999,1002,1005,1008,1010,1012,1014,1017,1019,1022,1025,1027,1030,1033],{"class":50,"line":51},[48,986,32],{"class":79},[48,988,82],{"class":58},[48,990,991],{"class":65},"\".sheet\"",[48,993,361],{"class":58},[48,995,996],{"class":79},"on",[48,998,82],{"class":58},[48,1000,1001],{"class":65},"\"dragend\"",[48,1003,1004],{"class":58},", ({ ",[48,1006,426],{"class":1007},"sQhOw",[48,1009,189],{"class":58},[48,1011,429],{"class":1007},[48,1013,189],{"class":58},[48,1015,1016],{"class":1007},"velocityX",[48,1018,189],{"class":58},[48,1020,1021],{"class":1007},"velocityY",[48,1023,1024],{"class":58}," }) ",[48,1026,710],{"class":54},[48,1028,1029],{"class":58}," { ",[48,1031,1032],{"class":276},"\u002F* … *\u002F",[48,1034,107],{"class":58},[10,1036,1037,1038,189,1041,189,1044,1047,1048,28],{},"Events: ",[25,1039,1040],{},"dragstart",[25,1042,1043],{},"drag",[25,1045,1046],{},"dragend",". See ",[962,1049,1051],{"href":1050},"\u002Fdocs\u002Fdrag\u002F#reacting-in-javascript","Drag",[30,1053,1055],{"id":1054},"cleaning-up","Cleaning up",[10,1057,1058,1061],{},[25,1059,1060],{},"destroy()"," removes the props and bindings a handle added. Motion declared in CSS keeps running. Elements removed from the page are released automatically.",[30,1063,1065],{"id":1064},"scopes","Scopes",[10,1067,1068,1071,1072,1074,1075,650],{},[25,1069,1070],{},"initKinesis()"," sets up every ",[25,1073,204],{}," element and watches the page for more. For a scope you control, for example in a component, use ",[25,1076,1077],{},"createKinesis()",[39,1079,1081],{"className":41,"code":1080,"language":43,"meta":44,"style":44},"import { createKinesis } from \"@amineyarman\u002Fkinesis\"\n\nconst scope = createKinesis(element)   \u002F\u002F adds data-kinesis if missing\n\nscope.pause()     \u002F\u002F freeze\nscope.resume()\nscope.refresh()   \u002F\u002F re-read CSS after changes Kinesis cannot observe\nscope.destroy()   \u002F\u002F remove everything and restore styles\n",[25,1082,1083,1094,1098,1116,1120,1134,1144,1157],{"__ignoreMap":44},[48,1084,1085,1087,1090,1092],{"class":50,"line":51},[48,1086,55],{"class":54},[48,1088,1089],{"class":58}," { createKinesis } ",[48,1091,62],{"class":54},[48,1093,66],{"class":65},[48,1095,1096],{"class":50,"line":69},[48,1097,73],{"emptyLinePlaceholder":72},[48,1099,1100,1102,1105,1107,1110,1113],{"class":50,"line":76},[48,1101,232],{"class":54},[48,1103,1104],{"class":91}," scope",[48,1106,238],{"class":54},[48,1108,1109],{"class":79}," createKinesis",[48,1111,1112],{"class":58},"(element)   ",[48,1114,1115],{"class":276},"\u002F\u002F adds data-kinesis if missing\n",[48,1117,1118],{"class":50,"line":110},[48,1119,73],{"emptyLinePlaceholder":72},[48,1121,1122,1125,1128,1131],{"class":50,"line":115},[48,1123,1124],{"class":58},"scope.",[48,1126,1127],{"class":79},"pause",[48,1129,1130],{"class":58},"()     ",[48,1132,1133],{"class":276},"\u002F\u002F freeze\n",[48,1135,1136,1138,1141],{"class":50,"line":134},[48,1137,1124],{"class":58},[48,1139,1140],{"class":79},"resume",[48,1142,1143],{"class":58},"()\n",[48,1145,1146,1148,1151,1154],{"class":50,"line":146},[48,1147,1124],{"class":58},[48,1149,1150],{"class":79},"refresh",[48,1152,1153],{"class":58},"()   ",[48,1155,1156],{"class":276},"\u002F\u002F re-read CSS after changes Kinesis cannot observe\n",[48,1158,1159,1161,1164,1166],{"class":50,"line":157},[48,1160,1124],{"class":58},[48,1162,1163],{"class":79},"destroy",[48,1165,1153],{"class":58},[48,1167,1168],{"class":276},"\u002F\u002F remove everything and restore styles\n",[30,1170,1172],{"id":1171},"options","Options",[39,1174,1176],{"className":41,"code":1175,"language":43,"meta":44,"style":44},"import { configure, initKinesis } from \"@amineyarman\u002Fkinesis\"\n\ninitKinesis({\n  reducedMotion: \"user\",   \u002F\u002F \"user\" (default), \"always\", or \"never\"\n  debug: false,            \u002F\u002F console warnings for common mistakes\n})\n\nconfigure({ reducedMotion: \"always\" })   \u002F\u002F change later\n",[25,1177,1178,1189,1193,1200,1214,1228,1232,1236],{"__ignoreMap":44},[48,1179,1180,1182,1185,1187],{"class":50,"line":51},[48,1181,55],{"class":54},[48,1183,1184],{"class":58}," { configure, initKinesis } ",[48,1186,62],{"class":54},[48,1188,66],{"class":65},[48,1190,1191],{"class":50,"line":69},[48,1192,73],{"emptyLinePlaceholder":72},[48,1194,1195,1198],{"class":50,"line":76},[48,1196,1197],{"class":79},"initKinesis",[48,1199,366],{"class":58},[48,1201,1202,1205,1208,1211],{"class":50,"line":110},[48,1203,1204],{"class":58},"  reducedMotion: ",[48,1206,1207],{"class":65},"\"user\"",[48,1209,1210],{"class":58},",   ",[48,1212,1213],{"class":276},"\u002F\u002F \"user\" (default), \"always\", or \"never\"\n",[48,1215,1216,1219,1222,1225],{"class":50,"line":115},[48,1217,1218],{"class":58},"  debug: ",[48,1220,1221],{"class":91},"false",[48,1223,1224],{"class":58},",            ",[48,1226,1227],{"class":276},"\u002F\u002F console warnings for common mistakes\n",[48,1229,1230],{"class":50,"line":134},[48,1231,182],{"class":58},[48,1233,1234],{"class":50,"line":146},[48,1235,73],{"emptyLinePlaceholder":72},[48,1237,1238,1241,1244,1247,1249],{"class":50,"line":157},[48,1239,1240],{"class":79},"configure",[48,1242,1243],{"class":58},"({ reducedMotion: ",[48,1245,1246],{"class":65},"\"always\"",[48,1248,273],{"class":58},[48,1250,1251],{"class":276},"\u002F\u002F change later\n",[10,1253,1254,1255,28],{},"The full list is in the ",[962,1256,1258],{"href":1257},"\u002Fdocs\u002Freference\u002Fjavascript\u002F","JavaScript API reference",[1260,1261,1262],"style",{},"html pre.shiki code .suJrU, html code.shiki .suJrU{--shiki-default:#FF7B72}html pre.shiki code .sZEs4, html code.shiki .sZEs4{--shiki-default:#E6EDF3}html pre.shiki code .s9uIt, html code.shiki .s9uIt{--shiki-default:#A5D6FF}html pre.shiki code .sc3cj, html code.shiki .sc3cj{--shiki-default:#D2A8FF}html pre.shiki code .sFSAA, html code.shiki .sFSAA{--shiki-default:#79C0FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sH3jZ, html code.shiki .sH3jZ{--shiki-default:#8B949E}html pre.shiki code .sQhOw, html code.shiki .sQhOw{--shiki-default:#FFA657}",{"title":44,"searchDepth":76,"depth":76,"links":1264},[1265,1266,1267,1268,1269,1270,1271,1272],{"id":32,"depth":69,"text":27},{"id":208,"depth":69,"text":209},{"id":319,"depth":69,"text":320},{"id":501,"depth":69,"text":502},{"id":976,"depth":69,"text":977},{"id":1054,"depth":69,"text":1055},{"id":1064,"depth":69,"text":1065},{"id":1171,"depth":69,"text":1172},"The same vocabulary as props, plus live overrides, bindings to signals, drag events, and scopes.","md",{},"\u002Fdocs\u002Fjavascript","---\ntitle: JavaScript\ndescription: The same vocabulary as props, plus live overrides, bindings to signals, drag events, and scopes.\n---\n\nEvery CSS property has a JavaScript prop. Use JavaScript when motion depends on application state, or when CSS can't describe it.\n\nEvery example on this site comes both ways. Open its code and switch **Motion in** to **JavaScript** to see it written with `kinesis()`.\n\n## kinesis()\n\n`kinesis()` takes an element, a selector, or a list of elements, plus props. Props are the CSS names in camelCase. Lengths are numbers in px, angles in degrees, times in ms.\n\n```js\nimport { kinesis } from \"@amineyarman\u002Fkinesis\"\n\nkinesis(\".card\", { tilt: 8, area: \"self\", motion: \"soft\" })\n\nkinesis(document.querySelector(\".cta\"), {\n  magnetic: 0.35,\n  radius: 160,\n  when: \"press\",\n  scale: 0.94,\n})\n```\n\nArrays are pairs: `parallax: [30, 12]`, `y: [24, 0]`, `color: [\"#999\", \"tomato\"]`. A single value means \"from rest to this\", as in CSS.\n\n`kinesis()` works on any element, inside a `[data-kinesis]` scope or not. Outside a scope, the pointer area is the viewport.\n\n## set() and reset()\n\n`kinesis()` returns a handle. `set()` changes props, which override CSS for that element. `reset()` hands them back to CSS.\n\n```js\nconst card = kinesis(document.querySelector(\".card\"))\n\ncard.set({ tilt: 14 })   \u002F\u002F JavaScript owns tilt now\ncard.reset(\"tilt\")       \u002F\u002F back to whatever the stylesheet says\ncard.reset()             \u002F\u002F back to CSS for everything\n```\n\n`set()` doesn't touch the DOM or re-read styles, so it's cheap enough for every input event or animation frame. Prefer it to inline `--k-*` styles for state-driven motion.\n\n## bind()\n\n`bind()` drives an output from a **signal**, a number that changes over time. Bound values add to CSS effects. Scale and opacity multiply.\n\n```js\nimport { kinesis, pointer } from \"@amineyarman\u002Fkinesis\"\n\nkinesis(\".needle\").bind({\n  rotate: pointer.speed.map([0, 4000], [-90, 90]).spring(\"bouncy\"),\n})\n```\n\n::demo{id=\"velocity\"}\n::\n\nBindable channels: `x`, `y`, `z`, `rotate`, `rotateX`, `rotateY`, `scale`, `opacity`, `blur`, `progress`, and any CSS custom property starting with `--`. Binding `progress` drives the element's reaction outputs in place of its trigger.\n\n```js\nkinesis(\".meter\").bind({ \"--level\": audio.volume })\n```\n\n`unbind(\"rotate\")` removes one binding, `unbind()` removes all of them.\n\n## Signals\n\nSignals update once per frame. Kinesis provides these:\n\n| Signal | Value |\n| --- | --- |\n| `pointer.x`, `pointer.y` | position in the viewport, px |\n| `pointer.nx`, `pointer.ny` | position from -1 to 1 across the viewport |\n| `pointer.velocityX`, `pointer.velocityY`, `pointer.speed` | px per second, smoothed |\n| `pointer.present` | 1 while a pointer is over the page |\n| `scroll.y`, `scroll.progress`, `scroll.velocity` | document scroll |\n| `time` | seconds since the page loaded |\n| `handle.progress` | an element's reaction progress |\n| `handle.near` | how close the pointer is to that element, 1 at its center to 0 at its `radius` |\n| `handle.pointer.x`, `handle.pointer.y` | the pointer's offset from that element's resting center, px |\n\nMake your own with `value()` and combine signals with `computed()`:\n\n```js\nimport { computed, kinesis, value } from \"@amineyarman\u002Fkinesis\"\n\nconst volume = value(0)          \u002F\u002F set it from anywhere: volume.set(0.7)\nconst loud = computed(() => volume.get() * 2)\n\nkinesis(\".bars\").bind({ scale: loud.map([0, 1], [1, 1.5]) })\n```\n\nEvery signal has:\n\n- **`map([inMin, inMax], [outMin, outMax])`** maps linearly, clamped to the output range. A third argument `{ clamp: false }` extrapolates. A function also works: `map(v => v * v)`.\n- **`clamp(min, max)`**\n- **`spring(preset)`** follows the signal with a spring, from a preset name or `{ stiffness, damping }`.\n- **`get()`** returns the current value.\n- **`subscribe(fn)`** calls `fn` with each new value, at most once per frame, and returns an unsubscribe function.\n\nA signal is computed at most once per frame, however many elements read it. Signals nothing reads cost nothing.\n\nPer-element signals keep custom motion short. Grass that bends behind a fast pointer, most strongly near it:\n\n```js\nfor (const el of document.querySelectorAll(\".blade\")) {\n  const blade = kinesis(el, { radius: 220 })\n  blade.bind({\n    rotate: computed(() => (pointer.velocityX.get() \u002F 45) * blade.near.get()).spring(\"bouncy\"),\n  })\n}\n```\n\nThe robot arm on the [home page](\u002F#arm) aims two joints the same way, from `shoulder.pointer.x` and `shoulder.pointer.y`.\n\n## Events\n\n```js\nkinesis(\".sheet\").on(\"dragend\", ({ x, y, velocityX, velocityY }) => { \u002F* … *\u002F })\n```\n\nEvents: `dragstart`, `drag`, `dragend`. See [Drag](\u002Fdocs\u002Fdrag\u002F#reacting-in-javascript).\n\n## Cleaning up\n\n`destroy()` removes the props and bindings a handle added. Motion declared in CSS keeps running. Elements removed from the page are released automatically.\n\n## Scopes\n\n`initKinesis()` sets up every `[data-kinesis]` element and watches the page for more. For a scope you control, for example in a component, use `createKinesis()`:\n\n```js\nimport { createKinesis } from \"@amineyarman\u002Fkinesis\"\n\nconst scope = createKinesis(element)   \u002F\u002F adds data-kinesis if missing\n\nscope.pause()     \u002F\u002F freeze\nscope.resume()\nscope.refresh()   \u002F\u002F re-read CSS after changes Kinesis cannot observe\nscope.destroy()   \u002F\u002F remove everything and restore styles\n```\n\n## Options\n\n```js\nimport { configure, initKinesis } from \"@amineyarman\u002Fkinesis\"\n\ninitKinesis({\n  reducedMotion: \"user\",   \u002F\u002F \"user\" (default), \"always\", or \"never\"\n  debug: false,            \u002F\u002F console warnings for common mistakes\n})\n\nconfigure({ reducedMotion: \"always\" })   \u002F\u002F change later\n```\n\nThe full list is in the [JavaScript API reference](\u002Fdocs\u002Freference\u002Fjavascript\u002F).\n",{"title":5,"description":1273},"docs\u002Fjavascript","EKzvviA7ZzPx5IJggfQ2KV3iKsH2AjaCb6sFLs-B-jA",1791309902955]