[{"data":1,"prerenderedAt":3216},["ShallowReactive",2],{"page-\u002Fcomponents\u002F基于vueuse的扇形虚拟滚动容器":3},{"id":4,"title":5,"body":6,"description":12,"extension":3207,"meta":3208,"navigation":105,"path":3212,"seo":3213,"stem":3214,"__hash__":3215},"content\u002Fcomponents\u002F基于vueuse的扇形虚拟滚动容器.md","基于vueuse的扇形虚拟滚动容器",{"type":7,"value":8,"toc":3201},"minimark",[9,13,16,32,43,46,50,57,63,66,156,159,163,170,309,312,383,386,389,608,611,617,625,791,794,1159,1162,1276,1279,1282,1285,1417,1420,1426,1429,1558,1561,1564,1844,1847,1853,1856,2191,2194,2523,2526,3198],[10,11,12],"p",{},"在开发Nuxt Content博客的时候，我注意到这个扇形滚动容器实际上是很实用的，至少是数学层面上，对我这个数学白痴来说，过了这个路基本不可能再写出来，所以决定写下这个笔记来记录一下扇形容器和虚拟滚动容器。",[10,14,15],{},"一般虚拟滚动容器需要拆分为4个问题：",[17,18,19,23,26,29],"ol",{},[20,21,22],"li",{},"总内容高度是多少",[20,24,25],{},"当前滚动到了哪里",[20,27,28],{},"哪些item进入了可视区域",[20,30,31],{},"这些item应该放在什么位置",[33,34,39],"pre",{"className":35,"code":37,"language":38},[36],"language-text","10000 个 item\n每个 item 高度 50px\n\n总高度：\n\n10000 × 50 = 500000px\n\n但实际上浏览器不需要创建10000个DOM\n","text",[40,41,37],"code",{"__ignoreMap":42},"",[10,44,45],{},"假设viewport高度为600px，那么理论上只需要600\u002F 15 = 20个，再加上一些上下缓冲，例如：buffer = 5，最后可能只会渲染22个item。",[47,48,49],"h2",{"id":49},"核心数学关系",[10,51,52,53,56],{},"我们假设itemHeight高度为50，scrollTop为2370，那么第一个进入viewport附近的item是：\n",[40,54,55],{},"floor(scrollTop \u002F itemHeight)"," ，差不多就是47，如果viewport高度是600，那么可视的item数量就是12个。如果考虑上buffer，那么:",[33,58,61],{"className":59,"code":60,"language":38},[36],"renderStart = max(0, 47 - 5)\n            = 42\n\nrenderEnd = 47 + 12 + 5\n          = 64\n          \n所以真正从数据里取\n\nitems[42 ... 64]\n\n",[40,62,60],{"__ignoreMap":42},[10,64,65],{},"这样的话浏览器会认为容器就只有23 x 50这样高度。用户就无法滚到后面的item，虚拟滚动容器就会制造一个虚拟内容高度，结构类似于：",[33,67,71],{"className":68,"code":69,"language":70,"meta":42,"style":42},"language-html shiki shiki-themes github-light github-dark","\u003Cdiv class=\"viewport\">\n\n    \u003Cdiv class=\"content\">\n        \u003C!-- 实际只放 23 个 item -->\n    \u003C\u002Fdiv>\n\n\u003C\u002Fdiv>\n","html",[40,72,73,100,107,124,131,141,146],{"__ignoreMap":42},[74,75,78,82,86,90,93,97],"span",{"class":76,"line":77},"line",1,[74,79,81],{"class":80},"sVt8B","\u003C",[74,83,85],{"class":84},"s9eBZ","div",[74,87,89],{"class":88},"sScJk"," class",[74,91,92],{"class":80},"=",[74,94,96],{"class":95},"sZZnC","\"viewport\"",[74,98,99],{"class":80},">\n",[74,101,103],{"class":76,"line":102},2,[74,104,106],{"emptyLinePlaceholder":105},true,"\n",[74,108,110,113,115,117,119,122],{"class":76,"line":109},3,[74,111,112],{"class":80},"    \u003C",[74,114,85],{"class":84},[74,116,89],{"class":88},[74,118,92],{"class":80},[74,120,121],{"class":95},"\"content\"",[74,123,99],{"class":80},[74,125,127],{"class":76,"line":126},4,[74,128,130],{"class":129},"sJ8bj","        \u003C!-- 实际只放 23 个 item -->\n",[74,132,134,137,139],{"class":76,"line":133},5,[74,135,136],{"class":80},"    \u003C\u002F",[74,138,85],{"class":84},[74,140,99],{"class":80},[74,142,144],{"class":76,"line":143},6,[74,145,106],{"emptyLinePlaceholder":105},[74,147,149,152,154],{"class":76,"line":148},7,[74,150,151],{"class":80},"\u003C\u002F",[74,153,85],{"class":84},[74,155,99],{"class":80},[10,157,158],{},"content设置height500000px都没有问题，这样scroll会认为10000x50是真实存在的，然后真正的item会通过 transform去移动到正确位置。",[47,160,162],{"id":161},"vuseuse提供的方法","Vuseuse提供的方法",[10,164,165,166,169],{},"vueuse这里提供了一个很好用的方法，这是我们组件的基底：",[40,167,168],{},"useVirtualList()","，这是vueuse提供的一套scroll position -> virtual items响应式计算逻辑。",[33,171,175],{"className":172,"code":173,"language":174,"meta":42,"style":42},"language-ts shiki shiki-themes github-light github-dark","const {\n  list,\n  containerProps,\n  wrapperProps\n} = useVirtualList(...)\n\nVueUse 常见写法大概是：\n\nconst {\n  list,\n  containerProps,\n  wrapperProps,\n} = useVirtualList(items, {\n  itemHeight: 50,\n  overscan: 5,\n})\n","ts",[40,176,177,186,195,202,207,226,230,235,240,247,254,261,269,281,292,303],{"__ignoreMap":42},[74,178,179,183],{"class":76,"line":77},[74,180,182],{"class":181},"szBVR","const",[74,184,185],{"class":80}," {\n",[74,187,188,192],{"class":76,"line":102},[74,189,191],{"class":190},"sj4cs","  list",[74,193,194],{"class":80},",\n",[74,196,197,200],{"class":76,"line":109},[74,198,199],{"class":190},"  containerProps",[74,201,194],{"class":80},[74,203,204],{"class":76,"line":126},[74,205,206],{"class":190},"  wrapperProps\n",[74,208,209,212,214,217,220,223],{"class":76,"line":133},[74,210,211],{"class":80},"} ",[74,213,92],{"class":181},[74,215,216],{"class":88}," useVirtualList",[74,218,219],{"class":80},"(",[74,221,222],{"class":181},"...",[74,224,225],{"class":80},")\n",[74,227,228],{"class":76,"line":143},[74,229,106],{"emptyLinePlaceholder":105},[74,231,232],{"class":76,"line":148},[74,233,234],{"class":80},"VueUse 常见写法大概是：\n",[74,236,238],{"class":76,"line":237},8,[74,239,106],{"emptyLinePlaceholder":105},[74,241,243,245],{"class":76,"line":242},9,[74,244,182],{"class":181},[74,246,185],{"class":80},[74,248,250,252],{"class":76,"line":249},10,[74,251,191],{"class":190},[74,253,194],{"class":80},[74,255,257,259],{"class":76,"line":256},11,[74,258,199],{"class":190},[74,260,194],{"class":80},[74,262,264,267],{"class":76,"line":263},12,[74,265,266],{"class":190},"  wrapperProps",[74,268,194],{"class":80},[74,270,272,274,276,278],{"class":76,"line":271},13,[74,273,211],{"class":80},[74,275,92],{"class":181},[74,277,216],{"class":88},[74,279,280],{"class":80},"(items, {\n",[74,282,284,287,290],{"class":76,"line":283},14,[74,285,286],{"class":80},"  itemHeight: ",[74,288,289],{"class":190},"50",[74,291,194],{"class":80},[74,293,295,298,301],{"class":76,"line":294},15,[74,296,297],{"class":80},"  overscan: ",[74,299,300],{"class":190},"5",[74,302,194],{"class":80},[74,304,306],{"class":76,"line":305},16,[74,307,308],{"class":80},"})\n",[10,310,311],{},"list是当前应该渲染的数据，containerProps是外层滚动容器应该具备的属性，wrapperProps是内层虚拟容器应该具备的属性。",[33,313,317],{"className":314,"code":315,"language":316,"meta":42,"style":42},"language-vue shiki shiki-themes github-light github-dark","\u003Cdiv v-bind=\"containerProps\">\n\n  \u003Cdiv v-bind=\"wrapperProps\">\n\n    \u003C!-- 实际只有几十个 item -->\n\n  \u003C\u002Fdiv>\n\n\u003C\u002Fdiv>\n","vue",[40,318,319,340,344,349,353,358,362,371,375],{"__ignoreMap":42},[74,320,321,323,325,328,330,333,336,338],{"class":76,"line":77},[74,322,81],{"class":80},[74,324,85],{"class":84},[74,326,327],{"class":88}," v-bind",[74,329,92],{"class":80},[74,331,332],{"class":95},"\"",[74,334,335],{"class":80},"containerProps",[74,337,332],{"class":95},[74,339,99],{"class":80},[74,341,342],{"class":76,"line":102},[74,343,106],{"emptyLinePlaceholder":105},[74,345,346],{"class":76,"line":109},[74,347,348],{"class":80},"  \u003Cdiv v-bind=\"wrapperProps\">\n",[74,350,351],{"class":76,"line":126},[74,352,106],{"emptyLinePlaceholder":105},[74,354,355],{"class":76,"line":133},[74,356,357],{"class":80},"    \u003C!-- 实际只有几十个 item -->\n",[74,359,360],{"class":76,"line":143},[74,361,106],{"emptyLinePlaceholder":105},[74,363,364,367,369],{"class":76,"line":148},[74,365,366],{"class":80},"  \u003C\u002F",[74,368,85],{"class":84},[74,370,99],{"class":80},[74,372,373],{"class":76,"line":237},[74,374,106],{"emptyLinePlaceholder":105},[74,376,377,379,381],{"class":76,"line":242},[74,378,151],{"class":80},[74,380,85],{"class":84},[74,382,99],{"class":80},[47,384,385],{"id":385},"组件设计",[10,387,388],{},"我的虚拟滚动容器是一个扇形滚动容器，但是比较好地分离了扇形逻辑和虚拟滚动组件容器，所以组合起来使用还是比较方便地。",[33,390,392],{"className":172,"code":391,"language":174,"meta":42,"style":42},"\u002F** 页面滚动进度 0~1 *\u002F\nconst pageProgress = computed(() => {\n  if (typeof document === 'undefined') return 0\n  \u002F\u002F viewportH 是响应式的，窗口缩放会触发重算；scrollHeight 每次现读，\n  \u002F\u002F 页面内容变高后（懒加载图片、字体回填）下一次滚动就会自动修正。\n  const max = document.documentElement.scrollHeight - viewportH.value\n  if (max \u003C= 0) return 0\n  return Math.min(1, Math.max(0, windowY.value \u002F max))\n})\n\n\u002F** 整程页面对应列表里的多少个条目（0 = 整份列表） *\u002F\nconst span = computed(() =>\n  props.windowSpan > 0 ? props.windowSpan : Math.max(0, props.items.length - 1),\n)\n",[40,393,394,399,420,449,454,459,478,497,533,537,541,546,562,604],{"__ignoreMap":42},[74,395,396],{"class":76,"line":77},[74,397,398],{"class":129},"\u002F** 页面滚动进度 0~1 *\u002F\n",[74,400,401,403,406,409,412,415,418],{"class":76,"line":102},[74,402,182],{"class":181},[74,404,405],{"class":190}," pageProgress",[74,407,408],{"class":181}," =",[74,410,411],{"class":88}," computed",[74,413,414],{"class":80},"(() ",[74,416,417],{"class":181},"=>",[74,419,185],{"class":80},[74,421,422,425,428,431,434,437,440,443,446],{"class":76,"line":109},[74,423,424],{"class":181},"  if",[74,426,427],{"class":80}," (",[74,429,430],{"class":181},"typeof",[74,432,433],{"class":80}," document ",[74,435,436],{"class":181},"===",[74,438,439],{"class":95}," 'undefined'",[74,441,442],{"class":80},") ",[74,444,445],{"class":181},"return",[74,447,448],{"class":190}," 0\n",[74,450,451],{"class":76,"line":126},[74,452,453],{"class":129},"  \u002F\u002F viewportH 是响应式的，窗口缩放会触发重算；scrollHeight 每次现读，\n",[74,455,456],{"class":76,"line":133},[74,457,458],{"class":129},"  \u002F\u002F 页面内容变高后（懒加载图片、字体回填）下一次滚动就会自动修正。\n",[74,460,461,464,467,469,472,475],{"class":76,"line":143},[74,462,463],{"class":181},"  const",[74,465,466],{"class":190}," max",[74,468,408],{"class":181},[74,470,471],{"class":80}," document.documentElement.scrollHeight ",[74,473,474],{"class":181},"-",[74,476,477],{"class":80}," viewportH.value\n",[74,479,480,482,485,488,491,493,495],{"class":76,"line":148},[74,481,424],{"class":181},[74,483,484],{"class":80}," (max ",[74,486,487],{"class":181},"\u003C=",[74,489,490],{"class":190}," 0",[74,492,442],{"class":80},[74,494,445],{"class":181},[74,496,448],{"class":190},[74,498,499,502,505,508,510,513,516,519,521,524,527,530],{"class":76,"line":237},[74,500,501],{"class":181},"  return",[74,503,504],{"class":80}," Math.",[74,506,507],{"class":88},"min",[74,509,219],{"class":80},[74,511,512],{"class":190},"1",[74,514,515],{"class":80},", Math.",[74,517,518],{"class":88},"max",[74,520,219],{"class":80},[74,522,523],{"class":190},"0",[74,525,526],{"class":80},", windowY.value ",[74,528,529],{"class":181},"\u002F",[74,531,532],{"class":80}," max))\n",[74,534,535],{"class":76,"line":242},[74,536,308],{"class":80},[74,538,539],{"class":76,"line":249},[74,540,106],{"emptyLinePlaceholder":105},[74,542,543],{"class":76,"line":256},[74,544,545],{"class":129},"\u002F** 整程页面对应列表里的多少个条目（0 = 整份列表） *\u002F\n",[74,547,548,550,553,555,557,559],{"class":76,"line":263},[74,549,182],{"class":181},[74,551,552],{"class":190}," span",[74,554,408],{"class":181},[74,556,411],{"class":88},[74,558,414],{"class":80},[74,560,561],{"class":181},"=>\n",[74,563,564,567,570,572,575,578,581,583,585,587,589,592,595,598,601],{"class":76,"line":271},[74,565,566],{"class":80},"  props.windowSpan ",[74,568,569],{"class":181},">",[74,571,490],{"class":190},[74,573,574],{"class":181}," ?",[74,576,577],{"class":80}," props.windowSpan ",[74,579,580],{"class":181},":",[74,582,504],{"class":80},[74,584,518],{"class":88},[74,586,219],{"class":80},[74,588,523],{"class":190},[74,590,591],{"class":80},", props.items.",[74,593,594],{"class":190},"length",[74,596,597],{"class":181}," -",[74,599,600],{"class":190}," 1",[74,602,603],{"class":80},"),\n",[74,605,606],{"class":76,"line":283},[74,607,225],{"class":80},[10,609,610],{},"我们通过scrollHeight - 视口高度值就可以得到滚动进度，这个是个很常见地表达。windowSpan不是dom窗口宽度而是整个页面的滚动距离，对应列表中的多少个条目，假设items有100个，windowSpan是20个，那么对照起来就是这样：",[33,612,615],{"className":613,"code":614,"language":38},[36],"页面位置             列表 progress\n\n顶部       0%   →     0\n          25%   →     5\n          50%   →     10\n          75%   →     15\n底部      100%  →     20\n",[40,616,614],{"__ignoreMap":42},[10,618,619,620,624],{},"通过span，我们就可以根据pageProgress的进度来",[621,622,623],"strong",{},"换算前位索引","：",[33,626,628],{"className":172,"code":627,"language":174,"meta":42,"style":42},"\u002F** 由页面进度直接换算出的「前位」索引 —— 绝对值，不是增量 *\u002F\nconst windowIndex = computed(() => pageProgress.value * span.value)\n\u002F**\n * 窗口模式下把轨道同步到前位：直接写绝对值（不是增量）。\n * useVirtualList 只认 container 的 scrollTop，同步过去它才取得到正确的窗口。\n * 依据 CSSWG css-overflow：overflow:hidden 的滚动容器「滚轮不滚、脚本可滚」。\n *\u002F\nwatchEffect(() => {\n  if (props.driver !== 'window') return\n  const el = scrollerRef.value\n  if (!el) return\n  const next = windowIndex.value * props.itemHeight\n  if (Math.abs(el.scrollTop - next) > 0.5) el.scrollTop = next\n})\n",[40,629,630,635,659,664,669,674,679,684,695,713,725,739,756,787],{"__ignoreMap":42},[74,631,632],{"class":76,"line":77},[74,633,634],{"class":129},"\u002F** 由页面进度直接换算出的「前位」索引 —— 绝对值，不是增量 *\u002F\n",[74,636,637,639,642,644,646,648,650,653,656],{"class":76,"line":102},[74,638,182],{"class":181},[74,640,641],{"class":190}," windowIndex",[74,643,408],{"class":181},[74,645,411],{"class":88},[74,647,414],{"class":80},[74,649,417],{"class":181},[74,651,652],{"class":80}," pageProgress.value ",[74,654,655],{"class":181},"*",[74,657,658],{"class":80}," span.value)\n",[74,660,661],{"class":76,"line":109},[74,662,663],{"class":129},"\u002F**\n",[74,665,666],{"class":76,"line":126},[74,667,668],{"class":129}," * 窗口模式下把轨道同步到前位：直接写绝对值（不是增量）。\n",[74,670,671],{"class":76,"line":133},[74,672,673],{"class":129}," * useVirtualList 只认 container 的 scrollTop，同步过去它才取得到正确的窗口。\n",[74,675,676],{"class":76,"line":143},[74,677,678],{"class":129}," * 依据 CSSWG css-overflow：overflow:hidden 的滚动容器「滚轮不滚、脚本可滚」。\n",[74,680,681],{"class":76,"line":148},[74,682,683],{"class":129}," *\u002F\n",[74,685,686,689,691,693],{"class":76,"line":237},[74,687,688],{"class":88},"watchEffect",[74,690,414],{"class":80},[74,692,417],{"class":181},[74,694,185],{"class":80},[74,696,697,699,702,705,708,710],{"class":76,"line":242},[74,698,424],{"class":181},[74,700,701],{"class":80}," (props.driver ",[74,703,704],{"class":181},"!==",[74,706,707],{"class":95}," 'window'",[74,709,442],{"class":80},[74,711,712],{"class":181},"return\n",[74,714,715,717,720,722],{"class":76,"line":249},[74,716,463],{"class":181},[74,718,719],{"class":190}," el",[74,721,408],{"class":181},[74,723,724],{"class":80}," scrollerRef.value\n",[74,726,727,729,731,734,737],{"class":76,"line":256},[74,728,424],{"class":181},[74,730,427],{"class":80},[74,732,733],{"class":181},"!",[74,735,736],{"class":80},"el) ",[74,738,712],{"class":181},[74,740,741,743,746,748,751,753],{"class":76,"line":263},[74,742,463],{"class":181},[74,744,745],{"class":190}," next",[74,747,408],{"class":181},[74,749,750],{"class":80}," windowIndex.value ",[74,752,655],{"class":181},[74,754,755],{"class":80}," props.itemHeight\n",[74,757,758,760,763,766,769,771,774,776,779,782,784],{"class":76,"line":271},[74,759,424],{"class":181},[74,761,762],{"class":80}," (Math.",[74,764,765],{"class":88},"abs",[74,767,768],{"class":80},"(el.scrollTop ",[74,770,474],{"class":181},[74,772,773],{"class":80}," next) ",[74,775,569],{"class":181},[74,777,778],{"class":190}," 0.5",[74,780,781],{"class":80},") el.scrollTop ",[74,783,92],{"class":181},[74,785,786],{"class":80}," next\n",[74,788,789],{"class":76,"line":283},[74,790,308],{"class":80},[10,792,793],{},"这里有两种驱动模式，一个是跟随window的进度，一个是像是普通的容器那样跟随鼠标滚轮滑动。这里主要看第二种，我们通过scrollTop的高度 \u002F item的高度就可以得到个数，也就是索引。这里的观察者模式用于把scrollerRef的scrollTop强制同步到windowIndex所代表的位置。",[33,795,797],{"className":172,"code":796,"language":174,"meta":42,"style":42},"\u002F**\n * 容器 props。窗口模式下换成 overflow:hidden：\n * 它仍然是可以被脚本写 scrollTop 的滚动容器（\"用户不可滚、脚本可滚\"），\n * 所以页面滚动能独占这条轨道。\n *\n * 注意 overscroll-behavior 必须跟着一起放开：实测（Chrome）\n * 「overflow:hidden + overscroll-behavior:contain」会把滚轮整块吞掉 ——\n * 轨道不滚、页面也不滚，光标停在扇形上就彻底卡住。\n *\u002F\nconst scrollerProps = computed(() => {\n  const { style, ...rest } = containerProps\n  const base = (typeof style === 'object' && style !== null ? style : {}) as Record\u003Cstring, unknown>\n  const windowed = props.driver === 'window'\n  return {\n    ...rest,\n    style: {\n      ...base,\n      overflowY: windowed ? 'hidden' : 'auto',\n      overscrollBehavior: windowed ? 'auto' : 'contain',\n    },\n  }\n})\n\u002F**\n * 轨道 scrollTop 的上限 = 总高 − 容器高，不补留白的话末尾\n * 「容器高 \u002F itemHeight」条永远到不了前位。\n *\u002F\nconst endPadding = computed(() => {\n  if (typeof props.endPadding === 'number') return Math.max(0, props.endPadding)\n  if (props.endPadding === 'container') return boxH.value\n  return props.driver === 'window' ? boxH.value : 0\n})\n",[40,798,799,803,808,813,818,823,828,833,838,842,859,885,943,960,966,974,979,988,1008,1025,1031,1037,1042,1047,1053,1059,1064,1082,1114,1134,1154],{"__ignoreMap":42},[74,800,801],{"class":76,"line":77},[74,802,663],{"class":129},[74,804,805],{"class":76,"line":102},[74,806,807],{"class":129}," * 容器 props。窗口模式下换成 overflow:hidden：\n",[74,809,810],{"class":76,"line":109},[74,811,812],{"class":129}," * 它仍然是可以被脚本写 scrollTop 的滚动容器（\"用户不可滚、脚本可滚\"），\n",[74,814,815],{"class":76,"line":126},[74,816,817],{"class":129}," * 所以页面滚动能独占这条轨道。\n",[74,819,820],{"class":76,"line":133},[74,821,822],{"class":129}," *\n",[74,824,825],{"class":76,"line":143},[74,826,827],{"class":129}," * 注意 overscroll-behavior 必须跟着一起放开：实测（Chrome）\n",[74,829,830],{"class":76,"line":148},[74,831,832],{"class":129}," * 「overflow:hidden + overscroll-behavior:contain」会把滚轮整块吞掉 ——\n",[74,834,835],{"class":76,"line":237},[74,836,837],{"class":129}," * 轨道不滚、页面也不滚，光标停在扇形上就彻底卡住。\n",[74,839,840],{"class":76,"line":242},[74,841,683],{"class":129},[74,843,844,846,849,851,853,855,857],{"class":76,"line":249},[74,845,182],{"class":181},[74,847,848],{"class":190}," scrollerProps",[74,850,408],{"class":181},[74,852,411],{"class":88},[74,854,414],{"class":80},[74,856,417],{"class":181},[74,858,185],{"class":80},[74,860,861,863,866,869,872,874,877,880,882],{"class":76,"line":256},[74,862,463],{"class":181},[74,864,865],{"class":80}," { ",[74,867,868],{"class":190},"style",[74,870,871],{"class":80},", ",[74,873,222],{"class":181},[74,875,876],{"class":190},"rest",[74,878,879],{"class":80}," } ",[74,881,92],{"class":181},[74,883,884],{"class":80}," containerProps\n",[74,886,887,889,892,894,896,898,901,903,906,909,911,913,916,918,920,922,925,928,931,933,936,938,941],{"class":76,"line":263},[74,888,463],{"class":181},[74,890,891],{"class":190}," base",[74,893,408],{"class":181},[74,895,427],{"class":80},[74,897,430],{"class":181},[74,899,900],{"class":80}," style ",[74,902,436],{"class":181},[74,904,905],{"class":95}," 'object'",[74,907,908],{"class":181}," &&",[74,910,900],{"class":80},[74,912,704],{"class":181},[74,914,915],{"class":190}," null",[74,917,574],{"class":181},[74,919,900],{"class":80},[74,921,580],{"class":181},[74,923,924],{"class":80}," {}) ",[74,926,927],{"class":181},"as",[74,929,930],{"class":88}," Record",[74,932,81],{"class":80},[74,934,935],{"class":190},"string",[74,937,871],{"class":80},[74,939,940],{"class":190},"unknown",[74,942,99],{"class":80},[74,944,945,947,950,952,955,957],{"class":76,"line":271},[74,946,463],{"class":181},[74,948,949],{"class":190}," windowed",[74,951,408],{"class":181},[74,953,954],{"class":80}," props.driver ",[74,956,436],{"class":181},[74,958,959],{"class":95}," 'window'\n",[74,961,962,964],{"class":76,"line":283},[74,963,501],{"class":181},[74,965,185],{"class":80},[74,967,968,971],{"class":76,"line":294},[74,969,970],{"class":181},"    ...",[74,972,973],{"class":80},"rest,\n",[74,975,976],{"class":76,"line":305},[74,977,978],{"class":80},"    style: {\n",[74,980,982,985],{"class":76,"line":981},17,[74,983,984],{"class":181},"      ...",[74,986,987],{"class":80},"base,\n",[74,989,991,994,997,1000,1003,1006],{"class":76,"line":990},18,[74,992,993],{"class":80},"      overflowY: windowed ",[74,995,996],{"class":181},"?",[74,998,999],{"class":95}," 'hidden'",[74,1001,1002],{"class":181}," :",[74,1004,1005],{"class":95}," 'auto'",[74,1007,194],{"class":80},[74,1009,1011,1014,1016,1018,1020,1023],{"class":76,"line":1010},19,[74,1012,1013],{"class":80},"      overscrollBehavior: windowed ",[74,1015,996],{"class":181},[74,1017,1005],{"class":95},[74,1019,1002],{"class":181},[74,1021,1022],{"class":95}," 'contain'",[74,1024,194],{"class":80},[74,1026,1028],{"class":76,"line":1027},20,[74,1029,1030],{"class":80},"    },\n",[74,1032,1034],{"class":76,"line":1033},21,[74,1035,1036],{"class":80},"  }\n",[74,1038,1040],{"class":76,"line":1039},22,[74,1041,308],{"class":80},[74,1043,1045],{"class":76,"line":1044},23,[74,1046,663],{"class":129},[74,1048,1050],{"class":76,"line":1049},24,[74,1051,1052],{"class":129}," * 轨道 scrollTop 的上限 = 总高 − 容器高，不补留白的话末尾\n",[74,1054,1056],{"class":76,"line":1055},25,[74,1057,1058],{"class":129}," * 「容器高 \u002F itemHeight」条永远到不了前位。\n",[74,1060,1062],{"class":76,"line":1061},26,[74,1063,683],{"class":129},[74,1065,1067,1069,1072,1074,1076,1078,1080],{"class":76,"line":1066},27,[74,1068,182],{"class":181},[74,1070,1071],{"class":190}," endPadding",[74,1073,408],{"class":181},[74,1075,411],{"class":88},[74,1077,414],{"class":80},[74,1079,417],{"class":181},[74,1081,185],{"class":80},[74,1083,1085,1087,1089,1091,1094,1096,1099,1101,1103,1105,1107,1109,1111],{"class":76,"line":1084},28,[74,1086,424],{"class":181},[74,1088,427],{"class":80},[74,1090,430],{"class":181},[74,1092,1093],{"class":80}," props.endPadding ",[74,1095,436],{"class":181},[74,1097,1098],{"class":95}," 'number'",[74,1100,442],{"class":80},[74,1102,445],{"class":181},[74,1104,504],{"class":80},[74,1106,518],{"class":88},[74,1108,219],{"class":80},[74,1110,523],{"class":190},[74,1112,1113],{"class":80},", props.endPadding)\n",[74,1115,1117,1119,1122,1124,1127,1129,1131],{"class":76,"line":1116},29,[74,1118,424],{"class":181},[74,1120,1121],{"class":80}," (props.endPadding ",[74,1123,436],{"class":181},[74,1125,1126],{"class":95}," 'container'",[74,1128,442],{"class":80},[74,1130,445],{"class":181},[74,1132,1133],{"class":80}," boxH.value\n",[74,1135,1137,1139,1141,1143,1145,1147,1150,1152],{"class":76,"line":1136},30,[74,1138,501],{"class":181},[74,1140,954],{"class":80},[74,1142,436],{"class":181},[74,1144,707],{"class":95},[74,1146,574],{"class":181},[74,1148,1149],{"class":80}," boxH.value ",[74,1151,580],{"class":181},[74,1153,448],{"class":190},[74,1155,1157],{"class":76,"line":1156},31,[74,1158,308],{"class":80},[10,1160,1161],{},"同一个scroller再self模式下应该让用户滚动，window模式下不让用户滚动，和上一段代码结合起来看就知道这样设计的目的，window滚动以后，内部的scroller的位置也要同步过去并禁止用户直接滚动内部scroller。",[33,1163,1165],{"className":172,"code":1164,"language":174,"meta":42,"style":42},"function scrollToIndex(index: number, behavior: ScrollBehavior = 'auto') {\n  if (behavior === 'auto') {\n    scrollTo(index)\n    return\n  }\n  const el = scrollerRef.value\n  if (!el) return\n  el.scrollTo({ top: index * props.itemHeight, behavior })\n}\n",[40,1166,1167,1203,1216,1224,1229,1233,1243,1255,1271],{"__ignoreMap":42},[74,1168,1169,1172,1175,1177,1181,1183,1186,1188,1191,1193,1196,1198,1200],{"class":76,"line":77},[74,1170,1171],{"class":181},"function",[74,1173,1174],{"class":88}," scrollToIndex",[74,1176,219],{"class":80},[74,1178,1180],{"class":1179},"s4XuR","index",[74,1182,580],{"class":181},[74,1184,1185],{"class":190}," number",[74,1187,871],{"class":80},[74,1189,1190],{"class":1179},"behavior",[74,1192,580],{"class":181},[74,1194,1195],{"class":88}," ScrollBehavior",[74,1197,408],{"class":181},[74,1199,1005],{"class":95},[74,1201,1202],{"class":80},") {\n",[74,1204,1205,1207,1210,1212,1214],{"class":76,"line":102},[74,1206,424],{"class":181},[74,1208,1209],{"class":80}," (behavior ",[74,1211,436],{"class":181},[74,1213,1005],{"class":95},[74,1215,1202],{"class":80},[74,1217,1218,1221],{"class":76,"line":109},[74,1219,1220],{"class":88},"    scrollTo",[74,1222,1223],{"class":80},"(index)\n",[74,1225,1226],{"class":76,"line":126},[74,1227,1228],{"class":181},"    return\n",[74,1230,1231],{"class":76,"line":133},[74,1232,1036],{"class":80},[74,1234,1235,1237,1239,1241],{"class":76,"line":143},[74,1236,463],{"class":181},[74,1238,719],{"class":190},[74,1240,408],{"class":181},[74,1242,724],{"class":80},[74,1244,1245,1247,1249,1251,1253],{"class":76,"line":148},[74,1246,424],{"class":181},[74,1248,427],{"class":80},[74,1250,733],{"class":181},[74,1252,736],{"class":80},[74,1254,712],{"class":181},[74,1256,1257,1260,1263,1266,1268],{"class":76,"line":237},[74,1258,1259],{"class":80},"  el.",[74,1261,1262],{"class":88},"scrollTo",[74,1264,1265],{"class":80},"({ top: index ",[74,1267,655],{"class":181},[74,1269,1270],{"class":80}," props.itemHeight, behavior })\n",[74,1272,1273],{"class":76,"line":242},[74,1274,1275],{"class":80},"}\n",[10,1277,1278],{},"这端函数就是这个组件对外暴露的滚动到第几个item的封装，根据behavoir分为了两个模式，index是列表索引，behavoir是滚动方式，auto模式会走scrollTo，这个是通过useVirtualLsit得到的，这里可以直接复用它提供的滚动方法，这样我们就不用自己重复实现vueuse已经提供的index->scrollTo的转换了。那么非auto需要自己调用el.scrollTo，原生DOM提供了浏览器的平滑滚动的能力。",[47,1280,1281],{"id":1281},"扇形组件",[10,1283,1284],{},"到刚才那一步，FanVirtualList的实现机制也就差不多了，接下来该看useFanArc.ts了，扇形容器十分依赖这个方法：",[33,1286,1288],{"className":172,"code":1287,"language":174,"meta":42,"style":42},"export function useFanArc(options: FanArcOptions) {\n  \u002F** 每条跨越的圆心角（弧度）—— 「固定角度」就来自这里 *\u002F\n  const stepRad = computed(() => toValue(options.itemHeight) \u002F toValue(options.radius))\n  const stepDeg = computed(() => (stepRad.value * 180) \u002F Math.PI)\n  const maxAngleRad = computed(() => (toValue(options.maxAngle) * Math.PI) \u002F 180)\n",[40,1289,1290,1313,1318,1346,1380],{"__ignoreMap":42},[74,1291,1292,1295,1298,1301,1303,1306,1308,1311],{"class":76,"line":77},[74,1293,1294],{"class":181},"export",[74,1296,1297],{"class":181}," function",[74,1299,1300],{"class":88}," useFanArc",[74,1302,219],{"class":80},[74,1304,1305],{"class":1179},"options",[74,1307,580],{"class":181},[74,1309,1310],{"class":88}," FanArcOptions",[74,1312,1202],{"class":80},[74,1314,1315],{"class":76,"line":102},[74,1316,1317],{"class":129},"  \u002F** 每条跨越的圆心角（弧度）—— 「固定角度」就来自这里 *\u002F\n",[74,1319,1320,1322,1325,1327,1329,1331,1333,1336,1339,1341,1343],{"class":76,"line":109},[74,1321,463],{"class":181},[74,1323,1324],{"class":190}," stepRad",[74,1326,408],{"class":181},[74,1328,411],{"class":88},[74,1330,414],{"class":80},[74,1332,417],{"class":181},[74,1334,1335],{"class":88}," toValue",[74,1337,1338],{"class":80},"(options.itemHeight) ",[74,1340,529],{"class":181},[74,1342,1335],{"class":88},[74,1344,1345],{"class":80},"(options.radius))\n",[74,1347,1348,1350,1353,1355,1357,1359,1361,1364,1366,1369,1371,1373,1375,1378],{"class":76,"line":126},[74,1349,463],{"class":181},[74,1351,1352],{"class":190}," stepDeg",[74,1354,408],{"class":181},[74,1356,411],{"class":88},[74,1358,414],{"class":80},[74,1360,417],{"class":181},[74,1362,1363],{"class":80}," (stepRad.value ",[74,1365,655],{"class":181},[74,1367,1368],{"class":190}," 180",[74,1370,442],{"class":80},[74,1372,529],{"class":181},[74,1374,504],{"class":80},[74,1376,1377],{"class":190},"PI",[74,1379,225],{"class":80},[74,1381,1382,1384,1387,1389,1391,1393,1395,1397,1400,1403,1405,1407,1409,1411,1413,1415],{"class":76,"line":133},[74,1383,463],{"class":181},[74,1385,1386],{"class":190}," maxAngleRad",[74,1388,408],{"class":181},[74,1390,411],{"class":88},[74,1392,414],{"class":80},[74,1394,417],{"class":181},[74,1396,427],{"class":80},[74,1398,1399],{"class":88},"toValue",[74,1401,1402],{"class":80},"(options.maxAngle) ",[74,1404,655],{"class":181},[74,1406,504],{"class":80},[74,1408,1377],{"class":190},[74,1410,442],{"class":80},[74,1412,529],{"class":181},[74,1414,1368],{"class":190},[74,1416,225],{"class":80},[10,1418,1419],{},"这里面的三个属性主要是解决相邻item再圆弧上差的角度，扇形最多允许张开多少角度。stepRad是一个item占的弧度，这里可以用园弧长公式：s=rθ 所以：θ=rs。这里s = itemHeight，r=radius，所以可以得到stepRad。​stepDeg可以把弧度转换成角度，css中rotate使用的是deg，数学计算中sin\u002Fcos使用的正是弧度。",[33,1421,1424],{"className":1422,"code":1423,"language":38},[36],"                 radius\n                   │\n                   ↓\nitemHeight ───→ stepRad ───→ 每个 item 的角度间隔\n                                  │\n                                  ↓\n                              angleOf(index)\n                                  │\n                                  ↓\n                                theta\n                                  │\n                    ┌─────────────┴─────────────┐\n                    ↓                           ↓\n                 sin\u002Fcos                    maxAngleRad\n                    ↓                           ↓\n                  x \u002F y                     visible\n",[40,1425,1423],{"__ignoreMap":42},[10,1427,1428],{},"然后",[33,1430,1432],{"className":172,"code":1431,"language":174,"meta":42,"style":42},"function angleOf(index: number) {\n  return (toValue(options.progress) - index) * stepRad.value\n}\n  function distanceOf(index: number) {\n    const max = maxAngleRad.value\n    if (max \u003C= 0) return 1\n    return Math.min(1, Math.abs(angleOf(index)) \u002F max)\n  }\n",[40,1433,1434,1451,1472,1476,1494,1506,1524,1554],{"__ignoreMap":42},[74,1435,1436,1438,1441,1443,1445,1447,1449],{"class":76,"line":77},[74,1437,1171],{"class":181},[74,1439,1440],{"class":88}," angleOf",[74,1442,219],{"class":80},[74,1444,1180],{"class":1179},[74,1446,580],{"class":181},[74,1448,1185],{"class":190},[74,1450,1202],{"class":80},[74,1452,1453,1455,1457,1459,1462,1464,1467,1469],{"class":76,"line":102},[74,1454,501],{"class":181},[74,1456,427],{"class":80},[74,1458,1399],{"class":88},[74,1460,1461],{"class":80},"(options.progress) ",[74,1463,474],{"class":181},[74,1465,1466],{"class":80}," index) ",[74,1468,655],{"class":181},[74,1470,1471],{"class":80}," stepRad.value\n",[74,1473,1474],{"class":76,"line":109},[74,1475,1275],{"class":80},[74,1477,1478,1481,1484,1486,1488,1490,1492],{"class":76,"line":126},[74,1479,1480],{"class":181},"  function",[74,1482,1483],{"class":88}," distanceOf",[74,1485,219],{"class":80},[74,1487,1180],{"class":1179},[74,1489,580],{"class":181},[74,1491,1185],{"class":190},[74,1493,1202],{"class":80},[74,1495,1496,1499,1501,1503],{"class":76,"line":133},[74,1497,1498],{"class":181},"    const",[74,1500,466],{"class":190},[74,1502,408],{"class":181},[74,1504,1505],{"class":80}," maxAngleRad.value\n",[74,1507,1508,1511,1513,1515,1517,1519,1521],{"class":76,"line":143},[74,1509,1510],{"class":181},"    if",[74,1512,484],{"class":80},[74,1514,487],{"class":181},[74,1516,490],{"class":190},[74,1518,442],{"class":80},[74,1520,445],{"class":181},[74,1522,1523],{"class":190}," 1\n",[74,1525,1526,1529,1531,1533,1535,1537,1539,1541,1543,1546,1549,1551],{"class":76,"line":148},[74,1527,1528],{"class":181},"    return",[74,1530,504],{"class":80},[74,1532,507],{"class":88},[74,1534,219],{"class":80},[74,1536,512],{"class":190},[74,1538,515],{"class":80},[74,1540,765],{"class":88},[74,1542,219],{"class":80},[74,1544,1545],{"class":88},"angleOf",[74,1547,1548],{"class":80},"(index)) ",[74,1550,529],{"class":181},[74,1552,1553],{"class":80}," max)\n",[74,1555,1556],{"class":76,"line":237},[74,1557,1036],{"class":80},[10,1559,1560],{},"这里实际上就是通过当前item距离前卫几个位置 x 每个位置对应的弧度，就可以得到角度了。distanceof计算某个item离前卫有多远，并把这个距离归一化成0~1。",[10,1562,1563],{},"到这里，还需要一个真正把角度换算成屏幕上x\u002Fy左边的方法：",[33,1565,1567],{"className":172,"code":1566,"language":174,"meta":42,"style":42},"  function positionOf(index: number): FanArcPosition {\n    const theta = angleOf(index)\n    const r = toValue(options.radius)\n    \u002F\u002F 圆心在「锚点向右 r」处：theta = 0 时条目正好落在锚点，向两端张开并轻微右凸\n    const x = toValue(options.anchorX) + r * (1 - Math.cos(theta))\n    const y = toValue(options.anchorY) - r * Math.sin(theta)\n    const thetaDeg = (theta * 180) \u002F Math.PI\n    const orientation = toValue(options.orientation)\n    const rot =\n      orientation === 'upright' ? 0 : orientation === 'radial' ? thetaDeg - 90 : thetaDeg\n    return {\n      x,\n      y,\n      rot,\n      theta,\n      thetaDeg,\n      visible: Math.abs(theta) \u003C= maxAngleRad.value,\n      distance: distanceOf(index),\n    }\n  }\n",[40,1568,1569,1594,1607,1621,1626,1662,1690,1715,1729,1739,1778,1784,1789,1794,1799,1804,1809,1824,1835,1840],{"__ignoreMap":42},[74,1570,1571,1573,1576,1578,1580,1582,1584,1587,1589,1592],{"class":76,"line":77},[74,1572,1480],{"class":181},[74,1574,1575],{"class":88}," positionOf",[74,1577,219],{"class":80},[74,1579,1180],{"class":1179},[74,1581,580],{"class":181},[74,1583,1185],{"class":190},[74,1585,1586],{"class":80},")",[74,1588,580],{"class":181},[74,1590,1591],{"class":88}," FanArcPosition",[74,1593,185],{"class":80},[74,1595,1596,1598,1601,1603,1605],{"class":76,"line":102},[74,1597,1498],{"class":181},[74,1599,1600],{"class":190}," theta",[74,1602,408],{"class":181},[74,1604,1440],{"class":88},[74,1606,1223],{"class":80},[74,1608,1609,1611,1614,1616,1618],{"class":76,"line":109},[74,1610,1498],{"class":181},[74,1612,1613],{"class":190}," r",[74,1615,408],{"class":181},[74,1617,1335],{"class":88},[74,1619,1620],{"class":80},"(options.radius)\n",[74,1622,1623],{"class":76,"line":126},[74,1624,1625],{"class":129},"    \u002F\u002F 圆心在「锚点向右 r」处：theta = 0 时条目正好落在锚点，向两端张开并轻微右凸\n",[74,1627,1628,1630,1633,1635,1637,1640,1643,1646,1648,1650,1652,1654,1656,1659],{"class":76,"line":133},[74,1629,1498],{"class":181},[74,1631,1632],{"class":190}," x",[74,1634,408],{"class":181},[74,1636,1335],{"class":88},[74,1638,1639],{"class":80},"(options.anchorX) ",[74,1641,1642],{"class":181},"+",[74,1644,1645],{"class":80}," r ",[74,1647,655],{"class":181},[74,1649,427],{"class":80},[74,1651,512],{"class":190},[74,1653,597],{"class":181},[74,1655,504],{"class":80},[74,1657,1658],{"class":88},"cos",[74,1660,1661],{"class":80},"(theta))\n",[74,1663,1664,1666,1669,1671,1673,1676,1678,1680,1682,1684,1687],{"class":76,"line":143},[74,1665,1498],{"class":181},[74,1667,1668],{"class":190}," y",[74,1670,408],{"class":181},[74,1672,1335],{"class":88},[74,1674,1675],{"class":80},"(options.anchorY) ",[74,1677,474],{"class":181},[74,1679,1645],{"class":80},[74,1681,655],{"class":181},[74,1683,504],{"class":80},[74,1685,1686],{"class":88},"sin",[74,1688,1689],{"class":80},"(theta)\n",[74,1691,1692,1694,1697,1699,1702,1704,1706,1708,1710,1712],{"class":76,"line":148},[74,1693,1498],{"class":181},[74,1695,1696],{"class":190}," thetaDeg",[74,1698,408],{"class":181},[74,1700,1701],{"class":80}," (theta ",[74,1703,655],{"class":181},[74,1705,1368],{"class":190},[74,1707,442],{"class":80},[74,1709,529],{"class":181},[74,1711,504],{"class":80},[74,1713,1714],{"class":190},"PI\n",[74,1716,1717,1719,1722,1724,1726],{"class":76,"line":237},[74,1718,1498],{"class":181},[74,1720,1721],{"class":190}," orientation",[74,1723,408],{"class":181},[74,1725,1335],{"class":88},[74,1727,1728],{"class":80},"(options.orientation)\n",[74,1730,1731,1733,1736],{"class":76,"line":242},[74,1732,1498],{"class":181},[74,1734,1735],{"class":190}," rot",[74,1737,1738],{"class":181}," =\n",[74,1740,1741,1744,1746,1749,1751,1753,1755,1758,1760,1763,1765,1768,1770,1773,1775],{"class":76,"line":249},[74,1742,1743],{"class":80},"      orientation ",[74,1745,436],{"class":181},[74,1747,1748],{"class":95}," 'upright'",[74,1750,574],{"class":181},[74,1752,490],{"class":190},[74,1754,1002],{"class":181},[74,1756,1757],{"class":80}," orientation ",[74,1759,436],{"class":181},[74,1761,1762],{"class":95}," 'radial'",[74,1764,574],{"class":181},[74,1766,1767],{"class":80}," thetaDeg ",[74,1769,474],{"class":181},[74,1771,1772],{"class":190}," 90",[74,1774,1002],{"class":181},[74,1776,1777],{"class":80}," thetaDeg\n",[74,1779,1780,1782],{"class":76,"line":256},[74,1781,1528],{"class":181},[74,1783,185],{"class":80},[74,1785,1786],{"class":76,"line":263},[74,1787,1788],{"class":80},"      x,\n",[74,1790,1791],{"class":76,"line":271},[74,1792,1793],{"class":80},"      y,\n",[74,1795,1796],{"class":76,"line":283},[74,1797,1798],{"class":80},"      rot,\n",[74,1800,1801],{"class":76,"line":294},[74,1802,1803],{"class":80},"      theta,\n",[74,1805,1806],{"class":76,"line":305},[74,1807,1808],{"class":80},"      thetaDeg,\n",[74,1810,1811,1814,1816,1819,1821],{"class":76,"line":981},[74,1812,1813],{"class":80},"      visible: Math.",[74,1815,765],{"class":88},[74,1817,1818],{"class":80},"(theta) ",[74,1820,487],{"class":181},[74,1822,1823],{"class":80}," maxAngleRad.value,\n",[74,1825,1826,1829,1832],{"class":76,"line":990},[74,1827,1828],{"class":80},"      distance: ",[74,1830,1831],{"class":88},"distanceOf",[74,1833,1834],{"class":80},"(index),\n",[74,1836,1837],{"class":76,"line":1010},[74,1838,1839],{"class":80},"    }\n",[74,1841,1842],{"class":76,"line":1027},[74,1843,1036],{"class":80},[10,1845,1846],{},"拿到item角度之后取半径，这里的r是圆的半径，anchorX和anchorY不是圆心：",[33,1848,1851],{"className":1849,"code":1850,"language":38},[36],"假设\nanchorX = 100\nanchorY = 300\nradius = 200\n\n那么圆心就在：\n(100 + 200, 300)\n= (300, 300)\n\n                    圆心\n                     ●\n                     │\n                     │ 200\n                     │\n前位锚点              │\n   ●─────────────────●\n (100,300)          (300,300)\n",[40,1852,1850],{"__ignoreMap":42},[10,1854,1855],{},"然后计算得到x和y，transformOf方法会将x和y取出来，这里是x和y第一次被使用：",[33,1857,1859],{"className":172,"code":1858,"language":174,"meta":42,"style":42},"function transformOf(index: number) {\n  const { x, y, rot } = positionOf(index)\n\n  return `translate3d(${x.toFixed(2)}px, ${y.toFixed(2)}px, 0) translate(-50%, -50%) rotate(${rot.toFixed(2)}deg)`\n}\n\nfunction itemStyle(index: number) {\n  const pos = positionOf(index)\n  if (!pos.visible) return { display: 'none' }\n  return {\n    transform: transformOf(index),\n    opacity: String(1 - toValue(options.fade) * pos.distance),\n    zIndex: String(1000 - Math.round(Math.abs(pos.thetaDeg))),\n  }\n}\n像\nx = 126.79\ny = 200\nrot = 30\n最后生成的css是\ntransform:\n  translate3d(126.79px, 200px, 0)\n  translate(-50%, -50%)\n  rotate(30deg);\n",[40,1860,1861,1878,1905,1909,1964,1968,1972,1989,2002,2024,2030,2040,2064,2091,2095,2099,2104,2114,2124,2134,2139,2147,2159,2183],{"__ignoreMap":42},[74,1862,1863,1865,1868,1870,1872,1874,1876],{"class":76,"line":77},[74,1864,1171],{"class":181},[74,1866,1867],{"class":88}," transformOf",[74,1869,219],{"class":80},[74,1871,1180],{"class":1179},[74,1873,580],{"class":181},[74,1875,1185],{"class":190},[74,1877,1202],{"class":80},[74,1879,1880,1882,1884,1887,1889,1892,1894,1897,1899,1901,1903],{"class":76,"line":102},[74,1881,463],{"class":181},[74,1883,865],{"class":80},[74,1885,1886],{"class":190},"x",[74,1888,871],{"class":80},[74,1890,1891],{"class":190},"y",[74,1893,871],{"class":80},[74,1895,1896],{"class":190},"rot",[74,1898,879],{"class":80},[74,1900,92],{"class":181},[74,1902,1575],{"class":88},[74,1904,1223],{"class":80},[74,1906,1907],{"class":76,"line":109},[74,1908,106],{"emptyLinePlaceholder":105},[74,1910,1911,1913,1916,1918,1921,1924,1926,1929,1931,1934,1936,1938,1940,1942,1944,1946,1949,1951,1953,1955,1957,1959,1961],{"class":76,"line":126},[74,1912,501],{"class":181},[74,1914,1915],{"class":95}," `translate3d(${",[74,1917,1886],{"class":80},[74,1919,1920],{"class":95},".",[74,1922,1923],{"class":88},"toFixed",[74,1925,219],{"class":95},[74,1927,1928],{"class":190},"2",[74,1930,1586],{"class":95},[74,1932,1933],{"class":95},"}px, ${",[74,1935,1891],{"class":80},[74,1937,1920],{"class":95},[74,1939,1923],{"class":88},[74,1941,219],{"class":95},[74,1943,1928],{"class":190},[74,1945,1586],{"class":95},[74,1947,1948],{"class":95},"}px, 0) translate(-50%, -50%) rotate(${",[74,1950,1896],{"class":80},[74,1952,1920],{"class":95},[74,1954,1923],{"class":88},[74,1956,219],{"class":95},[74,1958,1928],{"class":190},[74,1960,1586],{"class":95},[74,1962,1963],{"class":95},"}deg)`\n",[74,1965,1966],{"class":76,"line":133},[74,1967,1275],{"class":80},[74,1969,1970],{"class":76,"line":143},[74,1971,106],{"emptyLinePlaceholder":105},[74,1973,1974,1976,1979,1981,1983,1985,1987],{"class":76,"line":148},[74,1975,1171],{"class":181},[74,1977,1978],{"class":88}," itemStyle",[74,1980,219],{"class":80},[74,1982,1180],{"class":1179},[74,1984,580],{"class":181},[74,1986,1185],{"class":190},[74,1988,1202],{"class":80},[74,1990,1991,1993,1996,1998,2000],{"class":76,"line":237},[74,1992,463],{"class":181},[74,1994,1995],{"class":190}," pos",[74,1997,408],{"class":181},[74,1999,1575],{"class":88},[74,2001,1223],{"class":80},[74,2003,2004,2006,2008,2010,2013,2015,2018,2021],{"class":76,"line":242},[74,2005,424],{"class":181},[74,2007,427],{"class":80},[74,2009,733],{"class":181},[74,2011,2012],{"class":80},"pos.visible) ",[74,2014,445],{"class":181},[74,2016,2017],{"class":80}," { display: ",[74,2019,2020],{"class":95},"'none'",[74,2022,2023],{"class":80}," }\n",[74,2025,2026,2028],{"class":76,"line":249},[74,2027,501],{"class":181},[74,2029,185],{"class":80},[74,2031,2032,2035,2038],{"class":76,"line":256},[74,2033,2034],{"class":80},"    transform: ",[74,2036,2037],{"class":88},"transformOf",[74,2039,1834],{"class":80},[74,2041,2042,2045,2048,2050,2052,2054,2056,2059,2061],{"class":76,"line":263},[74,2043,2044],{"class":80},"    opacity: ",[74,2046,2047],{"class":88},"String",[74,2049,219],{"class":80},[74,2051,512],{"class":190},[74,2053,597],{"class":181},[74,2055,1335],{"class":88},[74,2057,2058],{"class":80},"(options.fade) ",[74,2060,655],{"class":181},[74,2062,2063],{"class":80}," pos.distance),\n",[74,2065,2066,2069,2071,2073,2076,2078,2080,2083,2086,2088],{"class":76,"line":271},[74,2067,2068],{"class":80},"    zIndex: ",[74,2070,2047],{"class":88},[74,2072,219],{"class":80},[74,2074,2075],{"class":190},"1000",[74,2077,597],{"class":181},[74,2079,504],{"class":80},[74,2081,2082],{"class":88},"round",[74,2084,2085],{"class":80},"(Math.",[74,2087,765],{"class":88},[74,2089,2090],{"class":80},"(pos.thetaDeg))),\n",[74,2092,2093],{"class":76,"line":283},[74,2094,1036],{"class":80},[74,2096,2097],{"class":76,"line":294},[74,2098,1275],{"class":80},[74,2100,2101],{"class":76,"line":305},[74,2102,2103],{"class":80},"像\n",[74,2105,2106,2109,2111],{"class":76,"line":981},[74,2107,2108],{"class":80},"x ",[74,2110,92],{"class":181},[74,2112,2113],{"class":190}," 126.79\n",[74,2115,2116,2119,2121],{"class":76,"line":990},[74,2117,2118],{"class":80},"y ",[74,2120,92],{"class":181},[74,2122,2123],{"class":190}," 200\n",[74,2125,2126,2129,2131],{"class":76,"line":1010},[74,2127,2128],{"class":80},"rot ",[74,2130,92],{"class":181},[74,2132,2133],{"class":190}," 30\n",[74,2135,2136],{"class":76,"line":1027},[74,2137,2138],{"class":80},"最后生成的css是\n",[74,2140,2141,2144],{"class":76,"line":1033},[74,2142,2143],{"class":88},"transform",[74,2145,2146],{"class":80},":\n",[74,2148,2149,2152,2155,2157],{"class":76,"line":1039},[74,2150,2151],{"class":88},"  translate3d",[74,2153,2154],{"class":80},"(126.79px, 200px, ",[74,2156,523],{"class":190},[74,2158,225],{"class":80},[74,2160,2161,2164,2166,2168,2170,2173,2175,2177,2179,2181],{"class":76,"line":1044},[74,2162,2163],{"class":88},"  translate",[74,2165,219],{"class":80},[74,2167,474],{"class":181},[74,2169,289],{"class":190},[74,2171,2172],{"class":181},"%",[74,2174,871],{"class":80},[74,2176,474],{"class":181},[74,2178,289],{"class":190},[74,2180,2172],{"class":181},[74,2182,225],{"class":80},[74,2184,2185,2188],{"class":76,"line":1049},[74,2186,2187],{"class":88},"  rotate",[74,2189,2190],{"class":80},"(30deg);\n",[10,2192,2193],{},"itemStyle又调用transformOf，const pos...这里又拿了一次x\u002Fy，后面的transform: transforOf(index)又拿了一次，所以实际上同一个index 的positionOf会算两次，两次的结果应该是一样的，因为输入没有在中间改变。",[33,2195,2197],{"className":314,"code":2196,"language":316,"meta":42,"style":42},"\u003Ctemplate>\n  \u003Cdiv ref=\"root\" class=\"fan-list\" :data-driver=\"driver\">\n    \u003Cdiv v-bind=\"scrollerProps\" class=\"fan-list__scroller\">\n      \u003C!-- 视觉层：sticky 钉在滚动视口上，条目用 transform 摆到弧上 -->\n      \u003Cdiv class=\"fan-list__stage\" :style=\"{ height: `${boxH}px` }\">\n        \u003Cdiv\n          v-for=\"entry in list\"\n          :key=\"entry.index\"\n          class=\"fan-list__item\"\n          :style=\"arc.itemStyle(entry.index)\"\n        >\n          \u003Cslot\n            :item=\"entry.data\"\n            :index=\"entry.index\"\n            :active=\"Math.abs(progress - entry.index) \u003C 0.5\"\n            :distance=\"arc.distanceOf(entry.index)\"\n          \u002F>\n        \u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n      \u003C!-- 占位层：只负责撑出滚动高度（vueuse 自己维护 marginTop \u002F height） -->\n      \u003Cdiv v-bind=\"wrapperProps\" \u002F>\n      \u003C!--\n        末尾留白：让最后的条目也能滚到前位（见脚本里 endPadding 的注释）。\n      -->\n      \u003Cdiv\n        v-if=\"endPadding > 0\"\n        class=\"fan-list__tail\"\n        :style=\"{ height: `${endPadding}px` }\"\n      \u002F>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n",[40,2198,2199,2208,2240,2262,2267,2291,2299,2309,2319,2329,2339,2344,2352,2362,2371,2381,2391,2399,2408,2417,2422,2440,2445,2450,2455,2461,2471,2481,2491,2498,2506,2514],{"__ignoreMap":42},[74,2200,2201,2203,2206],{"class":76,"line":77},[74,2202,81],{"class":80},[74,2204,2205],{"class":84},"template",[74,2207,99],{"class":80},[74,2209,2210,2213,2215,2218,2220,2223,2225,2227,2230,2233,2235,2238],{"class":76,"line":102},[74,2211,2212],{"class":80},"  \u003C",[74,2214,85],{"class":84},[74,2216,2217],{"class":88}," ref",[74,2219,92],{"class":80},[74,2221,2222],{"class":95},"\"root\"",[74,2224,89],{"class":88},[74,2226,92],{"class":80},[74,2228,2229],{"class":95},"\"fan-list\"",[74,2231,2232],{"class":88}," :data-driver",[74,2234,92],{"class":80},[74,2236,2237],{"class":95},"\"driver\"",[74,2239,99],{"class":80},[74,2241,2242,2244,2246,2248,2250,2253,2255,2257,2260],{"class":76,"line":109},[74,2243,112],{"class":80},[74,2245,85],{"class":84},[74,2247,327],{"class":88},[74,2249,92],{"class":80},[74,2251,2252],{"class":95},"\"scrollerProps\"",[74,2254,89],{"class":88},[74,2256,92],{"class":80},[74,2258,2259],{"class":95},"\"fan-list__scroller\"",[74,2261,99],{"class":80},[74,2263,2264],{"class":76,"line":126},[74,2265,2266],{"class":129},"      \u003C!-- 视觉层：sticky 钉在滚动视口上，条目用 transform 摆到弧上 -->\n",[74,2268,2269,2272,2274,2276,2278,2281,2284,2286,2289],{"class":76,"line":133},[74,2270,2271],{"class":80},"      \u003C",[74,2273,85],{"class":84},[74,2275,89],{"class":88},[74,2277,92],{"class":80},[74,2279,2280],{"class":95},"\"fan-list__stage\"",[74,2282,2283],{"class":88}," :style",[74,2285,92],{"class":80},[74,2287,2288],{"class":95},"\"{ height: `${boxH}px` }\"",[74,2290,99],{"class":80},[74,2292,2293,2296],{"class":76,"line":143},[74,2294,2295],{"class":80},"        \u003C",[74,2297,2298],{"class":84},"div\n",[74,2300,2301,2304,2306],{"class":76,"line":148},[74,2302,2303],{"class":88},"          v-for",[74,2305,92],{"class":80},[74,2307,2308],{"class":95},"\"entry in list\"\n",[74,2310,2311,2314,2316],{"class":76,"line":237},[74,2312,2313],{"class":88},"          :key",[74,2315,92],{"class":80},[74,2317,2318],{"class":95},"\"entry.index\"\n",[74,2320,2321,2324,2326],{"class":76,"line":242},[74,2322,2323],{"class":88},"          class",[74,2325,92],{"class":80},[74,2327,2328],{"class":95},"\"fan-list__item\"\n",[74,2330,2331,2334,2336],{"class":76,"line":249},[74,2332,2333],{"class":88},"          :style",[74,2335,92],{"class":80},[74,2337,2338],{"class":95},"\"arc.itemStyle(entry.index)\"\n",[74,2340,2341],{"class":76,"line":256},[74,2342,2343],{"class":80},"        >\n",[74,2345,2346,2349],{"class":76,"line":263},[74,2347,2348],{"class":80},"          \u003C",[74,2350,2351],{"class":84},"slot\n",[74,2353,2354,2357,2359],{"class":76,"line":271},[74,2355,2356],{"class":88},"            :item",[74,2358,92],{"class":80},[74,2360,2361],{"class":95},"\"entry.data\"\n",[74,2363,2364,2367,2369],{"class":76,"line":283},[74,2365,2366],{"class":88},"            :index",[74,2368,92],{"class":80},[74,2370,2318],{"class":95},[74,2372,2373,2376,2378],{"class":76,"line":294},[74,2374,2375],{"class":88},"            :active",[74,2377,92],{"class":80},[74,2379,2380],{"class":95},"\"Math.abs(progress - entry.index) \u003C 0.5\"\n",[74,2382,2383,2386,2388],{"class":76,"line":305},[74,2384,2385],{"class":88},"            :distance",[74,2387,92],{"class":80},[74,2389,2390],{"class":95},"\"arc.distanceOf(entry.index)\"\n",[74,2392,2393,2397],{"class":76,"line":981},[74,2394,2396],{"class":2395},"s7hpK","          \u002F",[74,2398,99],{"class":80},[74,2400,2401,2404,2406],{"class":76,"line":990},[74,2402,2403],{"class":80},"        \u003C\u002F",[74,2405,85],{"class":84},[74,2407,99],{"class":80},[74,2409,2410,2413,2415],{"class":76,"line":1010},[74,2411,2412],{"class":80},"      \u003C\u002F",[74,2414,85],{"class":84},[74,2416,99],{"class":80},[74,2418,2419],{"class":76,"line":1027},[74,2420,2421],{"class":129},"      \u003C!-- 占位层：只负责撑出滚动高度（vueuse 自己维护 marginTop \u002F height） -->\n",[74,2423,2424,2426,2428,2430,2432,2435,2438],{"class":76,"line":1033},[74,2425,2271],{"class":80},[74,2427,85],{"class":84},[74,2429,327],{"class":88},[74,2431,92],{"class":80},[74,2433,2434],{"class":95},"\"wrapperProps\"",[74,2436,2437],{"class":2395}," \u002F",[74,2439,99],{"class":80},[74,2441,2442],{"class":76,"line":1039},[74,2443,2444],{"class":129},"      \u003C!--\n",[74,2446,2447],{"class":76,"line":1044},[74,2448,2449],{"class":129},"        末尾留白：让最后的条目也能滚到前位（见脚本里 endPadding 的注释）。\n",[74,2451,2452],{"class":76,"line":1049},[74,2453,2454],{"class":129},"      -->\n",[74,2456,2457,2459],{"class":76,"line":1055},[74,2458,2271],{"class":80},[74,2460,2298],{"class":84},[74,2462,2463,2466,2468],{"class":76,"line":1061},[74,2464,2465],{"class":88},"        v-if",[74,2467,92],{"class":80},[74,2469,2470],{"class":95},"\"endPadding > 0\"\n",[74,2472,2473,2476,2478],{"class":76,"line":1066},[74,2474,2475],{"class":88},"        class",[74,2477,92],{"class":80},[74,2479,2480],{"class":95},"\"fan-list__tail\"\n",[74,2482,2483,2486,2488],{"class":76,"line":1084},[74,2484,2485],{"class":88},"        :style",[74,2487,92],{"class":80},[74,2489,2490],{"class":95},"\"{ height: `${endPadding}px` }\"\n",[74,2492,2493,2496],{"class":76,"line":1116},[74,2494,2495],{"class":2395},"      \u002F",[74,2497,99],{"class":80},[74,2499,2500,2502,2504],{"class":76,"line":1136},[74,2501,136],{"class":80},[74,2503,85],{"class":84},[74,2505,99],{"class":80},[74,2507,2508,2510,2512],{"class":76,"line":1156},[74,2509,366],{"class":80},[74,2511,85],{"class":84},[74,2513,99],{"class":80},[74,2515,2517,2519,2521],{"class":76,"line":2516},32,[74,2518,151],{"class":80},[74,2520,2205],{"class":84},[74,2522,99],{"class":80},[10,2524,2525],{},"到这里我们就已经实现了一个扇形的容器，而且是一个带虚拟滚动的扇形容器。END。\n食用方法如下：",[33,2527,2529],{"className":314,"code":2528,"language":316,"meta":42,"style":42},"\u003Cscript setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useContentStore } from '~\u002Fstores\u002Fcontent'\nimport FanVirtualList from '~\u002Fcomponents\u002FFanVirtualList.vue'\nimport RightRail from '~\u002Fcomponents\u002FRightRail.vue'\nimport { canonicalPath } from '~\u002Futils\u002Fpath'\nconst store = useContentStore()\nconst route = useRoute()\nconst items = computed(() => store.summaries)\nconst currentPath = computed(() => canonicalPath(route.path))\n\nfunction isActive(path: string) {\n  return currentPath.value === path\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CRightRail>\n    \u003CFanVirtualList\n      class=\"fan\"\n      :items=\"items\"\n      driver=\"window\"\n      :item-height=\"56\"\n      :radius=\"520\"\n      :max-angle=\"40\"\n      :fade=\"0.85\"\n      orientation=\"tangent\"\n      :anchor-x=\"0.62\"\n      :anchor-y=\"0.5\"\n    >\n      \u003Ctemplate #default=\"{ item, active }\">\n        \u003CNuxtLink\n          :to=\"item.path\"\n          class=\"doc-link hidden truncate md:block\"\n          :class=\"{ 'doc-link--front': active, 'doc-link--active': isActive(item.path) }\"\n        >\n          {{ item.title }}\n        \u003C\u002FNuxtLink>\n      \u003C\u002Ftemplate>\n    \u003C\u002FFanVirtualList>\n  \u003C\u002FRightRail>\n\u003C\u002Ftemplate>\n\n\u003Cstyle lang=\"scss\" scoped>\n.fan {\n  height: min(100vh, 34rem);\n}\n.doc-link {\n  width: 8rem;\n  padding: 0.45rem 0.75rem;\n  border: 1px solid transparent;\n  border-radius: 0.5rem;\n  font-size: 0.8125rem;\n  line-height: 1.15rem;\n  text-decoration: none;\n  box-shadow: 0 0 0 0 transparent;\n}\n.doc-link--front {\n  box-shadow: 0 6px 18px -8px rgb(0 0 0 \u002F 0.35);\n}\n.doc-link--active {\n  font-weight: 600;\n}\n\u003C\u002Fstyle>\n",[40,2530,2531,2551,2565,2577,2589,2601,2613,2628,2642,2660,2681,2685,2704,2716,2720,2728,2732,2740,2749,2756,2766,2783,2793,2809,2825,2841,2857,2867,2883,2899,2904,2927,2934,2952,2962,2997,3002,3008,3018,3027,3037,3046,3055,3060,3079,3085,3091,3096,3102,3108,3114,3120,3126,3132,3138,3144,3150,3155,3161,3167,3172,3178,3184,3189],{"__ignoreMap":42},[74,2532,2533,2535,2538,2541,2544,2546,2549],{"class":76,"line":77},[74,2534,81],{"class":80},[74,2536,2537],{"class":84},"script",[74,2539,2540],{"class":88}," setup",[74,2542,2543],{"class":88}," lang",[74,2545,92],{"class":80},[74,2547,2548],{"class":95},"\"ts\"",[74,2550,99],{"class":80},[74,2552,2553,2556,2559,2562],{"class":76,"line":102},[74,2554,2555],{"class":181},"import",[74,2557,2558],{"class":80}," { computed } ",[74,2560,2561],{"class":181},"from",[74,2563,2564],{"class":95}," 'vue'\n",[74,2566,2567,2569,2572,2574],{"class":76,"line":109},[74,2568,2555],{"class":181},[74,2570,2571],{"class":80}," { useContentStore } ",[74,2573,2561],{"class":181},[74,2575,2576],{"class":95}," '~\u002Fstores\u002Fcontent'\n",[74,2578,2579,2581,2584,2586],{"class":76,"line":126},[74,2580,2555],{"class":181},[74,2582,2583],{"class":80}," FanVirtualList ",[74,2585,2561],{"class":181},[74,2587,2588],{"class":95}," '~\u002Fcomponents\u002FFanVirtualList.vue'\n",[74,2590,2591,2593,2596,2598],{"class":76,"line":133},[74,2592,2555],{"class":181},[74,2594,2595],{"class":80}," RightRail ",[74,2597,2561],{"class":181},[74,2599,2600],{"class":95}," '~\u002Fcomponents\u002FRightRail.vue'\n",[74,2602,2603,2605,2608,2610],{"class":76,"line":143},[74,2604,2555],{"class":181},[74,2606,2607],{"class":80}," { canonicalPath } ",[74,2609,2561],{"class":181},[74,2611,2612],{"class":95}," '~\u002Futils\u002Fpath'\n",[74,2614,2615,2617,2620,2622,2625],{"class":76,"line":148},[74,2616,182],{"class":181},[74,2618,2619],{"class":190}," store",[74,2621,408],{"class":181},[74,2623,2624],{"class":88}," useContentStore",[74,2626,2627],{"class":80},"()\n",[74,2629,2630,2632,2635,2637,2640],{"class":76,"line":237},[74,2631,182],{"class":181},[74,2633,2634],{"class":190}," route",[74,2636,408],{"class":181},[74,2638,2639],{"class":88}," useRoute",[74,2641,2627],{"class":80},[74,2643,2644,2646,2649,2651,2653,2655,2657],{"class":76,"line":242},[74,2645,182],{"class":181},[74,2647,2648],{"class":190}," items",[74,2650,408],{"class":181},[74,2652,411],{"class":88},[74,2654,414],{"class":80},[74,2656,417],{"class":181},[74,2658,2659],{"class":80}," store.summaries)\n",[74,2661,2662,2664,2667,2669,2671,2673,2675,2678],{"class":76,"line":249},[74,2663,182],{"class":181},[74,2665,2666],{"class":190}," currentPath",[74,2668,408],{"class":181},[74,2670,411],{"class":88},[74,2672,414],{"class":80},[74,2674,417],{"class":181},[74,2676,2677],{"class":88}," canonicalPath",[74,2679,2680],{"class":80},"(route.path))\n",[74,2682,2683],{"class":76,"line":256},[74,2684,106],{"emptyLinePlaceholder":105},[74,2686,2687,2689,2692,2694,2697,2699,2702],{"class":76,"line":263},[74,2688,1171],{"class":181},[74,2690,2691],{"class":88}," isActive",[74,2693,219],{"class":80},[74,2695,2696],{"class":1179},"path",[74,2698,580],{"class":181},[74,2700,2701],{"class":190}," string",[74,2703,1202],{"class":80},[74,2705,2706,2708,2711,2713],{"class":76,"line":271},[74,2707,501],{"class":181},[74,2709,2710],{"class":80}," currentPath.value ",[74,2712,436],{"class":181},[74,2714,2715],{"class":80}," path\n",[74,2717,2718],{"class":76,"line":283},[74,2719,1275],{"class":80},[74,2721,2722,2724,2726],{"class":76,"line":294},[74,2723,151],{"class":80},[74,2725,2537],{"class":84},[74,2727,99],{"class":80},[74,2729,2730],{"class":76,"line":305},[74,2731,106],{"emptyLinePlaceholder":105},[74,2733,2734,2736,2738],{"class":76,"line":981},[74,2735,81],{"class":80},[74,2737,2205],{"class":84},[74,2739,99],{"class":80},[74,2741,2742,2744,2747],{"class":76,"line":990},[74,2743,2212],{"class":80},[74,2745,2746],{"class":84},"RightRail",[74,2748,99],{"class":80},[74,2750,2751,2753],{"class":76,"line":1010},[74,2752,112],{"class":80},[74,2754,2755],{"class":84},"FanVirtualList\n",[74,2757,2758,2761,2763],{"class":76,"line":1027},[74,2759,2760],{"class":88},"      class",[74,2762,92],{"class":80},[74,2764,2765],{"class":95},"\"fan\"\n",[74,2767,2768,2771,2774,2776,2778,2780],{"class":76,"line":1033},[74,2769,2770],{"class":80},"      :",[74,2772,2773],{"class":88},"items",[74,2775,92],{"class":80},[74,2777,332],{"class":95},[74,2779,2773],{"class":80},[74,2781,2782],{"class":95},"\"\n",[74,2784,2785,2788,2790],{"class":76,"line":1039},[74,2786,2787],{"class":88},"      driver",[74,2789,92],{"class":80},[74,2791,2792],{"class":95},"\"window\"\n",[74,2794,2795,2797,2800,2802,2804,2807],{"class":76,"line":1044},[74,2796,2770],{"class":80},[74,2798,2799],{"class":88},"item-height",[74,2801,92],{"class":80},[74,2803,332],{"class":95},[74,2805,2806],{"class":190},"56",[74,2808,2782],{"class":95},[74,2810,2811,2813,2816,2818,2820,2823],{"class":76,"line":1049},[74,2812,2770],{"class":80},[74,2814,2815],{"class":88},"radius",[74,2817,92],{"class":80},[74,2819,332],{"class":95},[74,2821,2822],{"class":190},"520",[74,2824,2782],{"class":95},[74,2826,2827,2829,2832,2834,2836,2839],{"class":76,"line":1055},[74,2828,2770],{"class":80},[74,2830,2831],{"class":88},"max-angle",[74,2833,92],{"class":80},[74,2835,332],{"class":95},[74,2837,2838],{"class":190},"40",[74,2840,2782],{"class":95},[74,2842,2843,2845,2848,2850,2852,2855],{"class":76,"line":1061},[74,2844,2770],{"class":80},[74,2846,2847],{"class":88},"fade",[74,2849,92],{"class":80},[74,2851,332],{"class":95},[74,2853,2854],{"class":190},"0.85",[74,2856,2782],{"class":95},[74,2858,2859,2862,2864],{"class":76,"line":1066},[74,2860,2861],{"class":88},"      orientation",[74,2863,92],{"class":80},[74,2865,2866],{"class":95},"\"tangent\"\n",[74,2868,2869,2871,2874,2876,2878,2881],{"class":76,"line":1084},[74,2870,2770],{"class":80},[74,2872,2873],{"class":88},"anchor-x",[74,2875,92],{"class":80},[74,2877,332],{"class":95},[74,2879,2880],{"class":190},"0.62",[74,2882,2782],{"class":95},[74,2884,2885,2887,2890,2892,2894,2897],{"class":76,"line":1116},[74,2886,2770],{"class":80},[74,2888,2889],{"class":88},"anchor-y",[74,2891,92],{"class":80},[74,2893,332],{"class":95},[74,2895,2896],{"class":190},"0.5",[74,2898,2782],{"class":95},[74,2900,2901],{"class":76,"line":1136},[74,2902,2903],{"class":80},"    >\n",[74,2905,2906,2908,2910,2913,2916,2918,2920,2923,2925],{"class":76,"line":1156},[74,2907,2271],{"class":80},[74,2909,2205],{"class":84},[74,2911,2912],{"class":80}," #",[74,2914,2915],{"class":88},"default",[74,2917,92],{"class":80},[74,2919,332],{"class":95},[74,2921,2922],{"class":80},"{ item, active }",[74,2924,332],{"class":95},[74,2926,99],{"class":80},[74,2928,2929,2931],{"class":76,"line":2516},[74,2930,2295],{"class":80},[74,2932,2933],{"class":84},"NuxtLink\n",[74,2935,2937,2940,2943,2945,2947,2950],{"class":76,"line":2936},33,[74,2938,2939],{"class":80},"          :",[74,2941,2942],{"class":88},"to",[74,2944,92],{"class":80},[74,2946,332],{"class":95},[74,2948,2949],{"class":80},"item.path",[74,2951,2782],{"class":95},[74,2953,2955,2957,2959],{"class":76,"line":2954},34,[74,2956,2323],{"class":88},[74,2958,92],{"class":80},[74,2960,2961],{"class":95},"\"doc-link hidden truncate md:block\"\n",[74,2963,2965,2967,2970,2972,2974,2977,2980,2983,2986,2989,2992,2995],{"class":76,"line":2964},35,[74,2966,2939],{"class":80},[74,2968,2969],{"class":88},"class",[74,2971,92],{"class":80},[74,2973,332],{"class":95},[74,2975,2976],{"class":80},"{ ",[74,2978,2979],{"class":95},"'doc-link--front'",[74,2981,2982],{"class":80},": active, ",[74,2984,2985],{"class":95},"'doc-link--active'",[74,2987,2988],{"class":80},": ",[74,2990,2991],{"class":88},"isActive",[74,2993,2994],{"class":80},"(item.path) }",[74,2996,2782],{"class":95},[74,2998,3000],{"class":76,"line":2999},36,[74,3001,2343],{"class":80},[74,3003,3005],{"class":76,"line":3004},37,[74,3006,3007],{"class":80},"          {{ item.title }}\n",[74,3009,3011,3013,3016],{"class":76,"line":3010},38,[74,3012,2403],{"class":80},[74,3014,3015],{"class":84},"NuxtLink",[74,3017,99],{"class":80},[74,3019,3021,3023,3025],{"class":76,"line":3020},39,[74,3022,2412],{"class":80},[74,3024,2205],{"class":84},[74,3026,99],{"class":80},[74,3028,3030,3032,3035],{"class":76,"line":3029},40,[74,3031,136],{"class":80},[74,3033,3034],{"class":84},"FanVirtualList",[74,3036,99],{"class":80},[74,3038,3040,3042,3044],{"class":76,"line":3039},41,[74,3041,366],{"class":80},[74,3043,2746],{"class":84},[74,3045,99],{"class":80},[74,3047,3049,3051,3053],{"class":76,"line":3048},42,[74,3050,151],{"class":80},[74,3052,2205],{"class":84},[74,3054,99],{"class":80},[74,3056,3058],{"class":76,"line":3057},43,[74,3059,106],{"emptyLinePlaceholder":105},[74,3061,3063,3065,3067,3069,3071,3074,3077],{"class":76,"line":3062},44,[74,3064,81],{"class":80},[74,3066,868],{"class":84},[74,3068,2543],{"class":88},[74,3070,92],{"class":80},[74,3072,3073],{"class":95},"\"scss\"",[74,3075,3076],{"class":88}," scoped",[74,3078,99],{"class":80},[74,3080,3082],{"class":76,"line":3081},45,[74,3083,3084],{"class":80},".fan {\n",[74,3086,3088],{"class":76,"line":3087},46,[74,3089,3090],{"class":80},"  height: min(100vh, 34rem);\n",[74,3092,3094],{"class":76,"line":3093},47,[74,3095,1275],{"class":80},[74,3097,3099],{"class":76,"line":3098},48,[74,3100,3101],{"class":80},".doc-link {\n",[74,3103,3105],{"class":76,"line":3104},49,[74,3106,3107],{"class":80},"  width: 8rem;\n",[74,3109,3111],{"class":76,"line":3110},50,[74,3112,3113],{"class":80},"  padding: 0.45rem 0.75rem;\n",[74,3115,3117],{"class":76,"line":3116},51,[74,3118,3119],{"class":80},"  border: 1px solid transparent;\n",[74,3121,3123],{"class":76,"line":3122},52,[74,3124,3125],{"class":80},"  border-radius: 0.5rem;\n",[74,3127,3129],{"class":76,"line":3128},53,[74,3130,3131],{"class":80},"  font-size: 0.8125rem;\n",[74,3133,3135],{"class":76,"line":3134},54,[74,3136,3137],{"class":80},"  line-height: 1.15rem;\n",[74,3139,3141],{"class":76,"line":3140},55,[74,3142,3143],{"class":80},"  text-decoration: none;\n",[74,3145,3147],{"class":76,"line":3146},56,[74,3148,3149],{"class":80},"  box-shadow: 0 0 0 0 transparent;\n",[74,3151,3153],{"class":76,"line":3152},57,[74,3154,1275],{"class":80},[74,3156,3158],{"class":76,"line":3157},58,[74,3159,3160],{"class":80},".doc-link--front {\n",[74,3162,3164],{"class":76,"line":3163},59,[74,3165,3166],{"class":80},"  box-shadow: 0 6px 18px -8px rgb(0 0 0 \u002F 0.35);\n",[74,3168,3170],{"class":76,"line":3169},60,[74,3171,1275],{"class":80},[74,3173,3175],{"class":76,"line":3174},61,[74,3176,3177],{"class":80},".doc-link--active {\n",[74,3179,3181],{"class":76,"line":3180},62,[74,3182,3183],{"class":80},"  font-weight: 600;\n",[74,3185,3187],{"class":76,"line":3186},63,[74,3188,1275],{"class":80},[74,3190,3192,3194,3196],{"class":76,"line":3191},64,[74,3193,151],{"class":80},[74,3195,868],{"class":84},[74,3197,99],{"class":80},[868,3199,3200],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .s7hpK, html code.shiki .s7hpK{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic}",{"title":42,"searchDepth":102,"depth":102,"links":3202},[3203,3204,3205,3206],{"id":49,"depth":102,"text":49},{"id":161,"depth":102,"text":162},{"id":385,"depth":102,"text":385},{"id":1281,"depth":102,"text":1281},"md",{"date":3209,"tags":3210},"2026-05-10",[316,3211],"components","\u002Fcomponents\u002F基于vueuse的扇形虚拟滚动容器",{"description":12},"components\u002F基于vueuse的扇形虚拟滚动容器","ZgbXrBi4kgprTdO87Q5ohdtVlVqwH9KdZsMMtAgJ0Xw",1791207386953]