Skip to content

Commit

Permalink
* fix firefox compatibility: https://bugzilla.mozilla.org/show_bug.cg…
Browse files Browse the repository at this point in the history
…i?id=1918017 by watching the event listener on `window.resize` to re-calc the `window.height - topOffset` for the `rootMargin.bottom` of intersection observer @ stores/viewportTopmostPost.ts

* fix firefox compatibility by feature detecting on `Element.computedStyleMap()` with fallbacking to `.getComputedStyle()`, and replace all `Element.attributeStyleMap.set()` with old-style `.style.setProperty()` @ utils/post/renderer/list/index.ts
* replace the event listener on `window.resize` with `useResizeableEcharts()` that only observer resize events of given element(s) and replace `_.throttle` with `_.debounce` with a larger waiting window @ utils/echarts.ts
@ fe
  • Loading branch information
n0099 committed Sep 11, 2024
1 parent b433e18 commit 3bbae18
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 32 deletions.
1 change: 1 addition & 0 deletions fe/src/pages/bilibiliVote.vue
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ const chartElementRefs = {
top5CandidateCountGroupByTime: ref<HTMLElement>(),
allVoteCountGroupByTime: ref<HTMLElement>()
};
useResizeableEcharts(Object.values(chartElementRefs));
type ChartName = keyof typeof chartElementRefs;
const {
top50CandidateCount: top50CandidateCountRef,
Expand Down
36 changes: 21 additions & 15 deletions fe/src/stores/viewportTopmostPost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,30 @@ import _ from 'lodash';
export const useViewportTopmostPostStore = defineStore('viewportTopmostPost', () => {
interface TopmostPost { cursor: Cursor, tid: Tid, pid?: Pid }
const viewportTopmostPost = ref<TopmostPost>();

const { height: windowHeight } = useWindowSize();
const intersectionObserver = (newTopmostPost: TopmostPost, topOffset = 0) => {
const stickyTitleEl = ref<HTMLElement>();
useIntersectionObserver(stickyTitleEl, entries => {
_.orderBy(entries, 'time').forEach(e => { // https://github.com/vueuse/vueuse/issues/4197
if (e.isIntersecting
&& !(newTopmostPost.pid === undefined // prevent thread overwrite its reply
&& viewportTopmostPost.value?.tid === newTopmostPost.tid))
viewportTopmostPost.value = newTopmostPost;
});
// eslint-disable-next-line @typescript-eslint/no-empty-function
let stopExistingIntersectionObserver = () => {};
watchDebounced(windowHeight, () => {
stopExistingIntersectionObserver();
const { stop } = useIntersectionObserver(stickyTitleEl, entries => {
_.orderBy(entries, 'time').forEach(e => { // https://github.com/vueuse/vueuse/issues/4197
if (e.isIntersecting
&& !(newTopmostPost.pid === undefined // prevent thread overwrite its reply
&& viewportTopmostPost.value?.tid === newTopmostPost.tid))
viewportTopmostPost.value = newTopmostPost;
});

// bottom: -100% will only trigger when reaching the top border of root that defaults to viewport
// top: -(topOffset + 1)px will move down the trigger line below the top border to match with its offset
// the additional +1px will allow they sharing an intersection with 1px height when the stickyTitleEl is stucking
// https://stackoverflow.com/questions/16302483/event-to-detect-when-positionsticky-is-triggered
// https://stackoverflow.com/questions/54807535/intersection-observer-api-observe-the-center-of-the-viewport
// https://web.archive.org/web/20240111160426/https://wilsotobianco.com/experiments/intersection-observer-playground/
}, { rootMargin: `-${topOffset}px 0px -100% 0px` });
// bottom: -windowHeight will only trigger when reaching the top border of root that defaults to viewport
// additional +topOffset and not using -100% to fix https://bugzilla.mozilla.org/show_bug.cgi?id=1918017
// top: -topOffset will move down the trigger line below the top border to match with its offset
// https://stackoverflow.com/questions/16302483/event-to-detect-when-positionsticky-is-triggered
// https://stackoverflow.com/questions/54807535/intersection-observer-api-observe-the-center-of-the-viewport
// https://web.archive.org/web/20240111160426/https://wilsotobianco.com/experiments/intersection-observer-playground/
}, { rootMargin: `${-topOffset}px 0px ${-windowHeight.value + topOffset}px 0px` });
stopExistingIntersectionObserver = stop;
}, { debounce: 5000, immediate: true });

return { stickyTitleEl };
};
Expand Down
12 changes: 6 additions & 6 deletions fe/src/utils/echarts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import * as echarts from 'echarts/core';
// eslint-disable-next-line import-x/extensions
import type { ColorPaletteOptionMixin } from 'echarts/types/src/util/types.d.ts';

if (import.meta.client) {
addEventListener('resize', _.throttle(() => {
document.querySelectorAll<HTMLElement>('.echarts')
.forEach(el => { echarts.getInstanceByDom(el)?.resize() });
}, 200, { leading: false }));
}
export const useResizeableEcharts = (el: Parameters<typeof useResizeObserver>[0]) =>
useResizeObserver(el, _.debounce((entries: Parameters<Parameters<typeof useResizeObserver>[1]>[0]) => { // https://github.com/vueuse/vueuse/issues/4216
entries.forEach(entry => {
echarts.getInstanceByDom(entry.target as HTMLElement)?.resize();
});
}, 1000));

export const echarts4ColorTheme: ColorPaletteOptionMixin = {
color: [
Expand Down
42 changes: 31 additions & 11 deletions fe/src/utils/post/renderer/list/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,37 @@ export const guessReplyContainIntrinsicBlockSize = (replyElements: HTMLElement[]
if (contentEl === null)
return;
const contentStyles = ((el: HTMLElement) => {
const getCSSPropertyInPixels = (el: HTMLElement, property: string) =>
(el.computedStyleMap().get(property) as CSSNumericValue).to('px').value;
// https://caniuse.com/mdn-api_element_computedstylemap
// https://bugzilla.mozilla.org/show_bug.cgi?id=1857849
// https://github.com/surma/ishoudinireadyyet.com
// https://github.com/tylergaw/css-typed-om/
const isCSMSupported = 'computedStyleMap' in el;
const pixelStringToNumber = (s: string) => {
if (!s.endsWith('px'))
throw new Error(`Unit of '${s}' is not in pixels`);

return Number(removeEnd(s, 'px')); // parseInt() will also remove any suffix
};

const getCSSPropertyInPixels = (el: HTMLElement, property: string) => (isCSMSupported
? (el.computedStyleMap().get(property) as CSSNumericValue).to('px').value
: pixelStringToNumber(getComputedStyle(el).getPropertyValue(property)));
const getInnerWidth = (el: HTMLElement | null) => (el === null
? 0
: el.clientWidth - getCSSPropertyInPixels(el, 'padding-left')
- getCSSPropertyInPixels(el, 'padding-right'));
const contentLineHeightUnitValue = el.computedStyleMap().get('line-height') as CSSUnitValue;

const convertRemUnitValueToPixels = (unitValue: CSSUnitValue) => (unitValue.unit === 'number'
? convertRemToPixels(unitValue.value)
: unitValue.to('px').value);
const lineHeight = isCSMSupported
? convertRemUnitValueToPixels(el.computedStyleMap().get('line-height') as CSSUnitValue)
: convertRemToPixels(pixelStringToNumber(getComputedStyle(el).lineHeight));

return {
width: getInnerWidth(el),
subReply: { width: getInnerWidth(document.querySelector('.sub-reply-content')) },
lineHeight: contentLineHeightUnitValue.unit === 'number'
? convertRemToPixels(contentLineHeightUnitValue.value)
: contentLineHeightUnitValue.to('px').value
lineHeight
};
})(contentEl);

Expand Down Expand Up @@ -80,20 +97,23 @@ export const guessReplyContainIntrinsicBlockSize = (replyElements: HTMLElement[]
return Math.round(Math.ceil(lineCount) * contentStyles.lineHeight);
};
replyElements.forEach(el => {
el.attributeStyleMap.set('--sub-reply-group-count', el.querySelectorAll('.sub-reply-group').length);
// https://caniuse.com/mdn-api_htmlelement_attributestylemap
el.style.setProperty('--sub-reply-group-count',
el.querySelectorAll('.sub-reply-group').length.toString());

const imageLineCount = (el.querySelectorAll('.tieba-ugc-image').length * tiebaUGCImageMaxSize.px) / contentStyles.width;
el.attributeStyleMap.set('--predicted-image-height',
const imageLineCount = (el.querySelectorAll('.tieba-ugc-image')
.length * tiebaUGCImageMaxSize.px) / contentStyles.width;
el.style.setProperty('--predicted-image-height',
`${Math.ceil(imageLineCount) * tiebaUGCImageMaxSize.px}px`);

const replyContentHeight = predictPostContentHeight(contentStyles.width)(el.querySelector('.reply-content'));
el.attributeStyleMap.set('--predicted-reply-content-height', `${replyContentHeight}px`);
el.style.setProperty('--predicted-reply-content-height', `${replyContentHeight}px`);

const subReplyContentHeight = _.sum(
[...el.querySelectorAll<HTMLElement>('.sub-reply-content')]
.map(predictPostContentHeight(contentStyles.subReply.width))
);
el.attributeStyleMap.set('--predicted-sub-reply-content-height', `${subReplyContentHeight}px`);
el.style.setProperty('--predicted-sub-reply-content-height', `${subReplyContentHeight}px`);
});

// show diff between predicted height and actual height of each `.reply` after complete scroll over whole page
Expand Down

0 comments on commit 3bbae18

Please sign in to comment.