From 2a27144c3cad20143b985d15d2193d8933ee2521 Mon Sep 17 00:00:00 2001 From: Matt Perry Date: Thu, 12 Feb 2026 10:25:56 +0100 Subject: [PATCH 1/8] Fix useScroll setting accelerate when ScrollTimeline can't be used When target is provided, native ScrollTimeline isn't used, but accelerate was still set on scrollXProgress/scrollYProgress. This caused VisualElement.bindToMotionValue to take the WAAPI path, skipping the normal on("change") subscription and leaving latestValues stale. Co-Authored-By: Claude Opus 4.6 --- .../src/tests/scroll-target-transform.tsx | 44 +++++++++++++++ .../integration/scroll-target-transform.ts | 31 +++++++++++ .../scroll/utils/can-use-native-timeline.ts | 7 +++ .../render/dom/scroll/utils/get-timeline.ts | 5 +- .../framer-motion/src/value/use-scroll.ts | 53 ++++++++++--------- 5 files changed, 113 insertions(+), 27 deletions(-) create mode 100644 dev/react/src/tests/scroll-target-transform.tsx create mode 100644 packages/framer-motion/cypress/integration/scroll-target-transform.ts create mode 100644 packages/framer-motion/src/render/dom/scroll/utils/can-use-native-timeline.ts diff --git a/dev/react/src/tests/scroll-target-transform.tsx b/dev/react/src/tests/scroll-target-transform.tsx new file mode 100644 index 0000000000..8a078255fb --- /dev/null +++ b/dev/react/src/tests/scroll-target-transform.tsx @@ -0,0 +1,44 @@ +import { motion, useScroll, useTransform } from "framer-motion" +import * as React from "react" +import { useRef } from "react" + +export const App = () => { + const targetRef = useRef(null) + const { scrollYProgress } = useScroll({ + target: targetRef, + offset: ["start end", "end start"], + }) + + const opacity = useTransform(scrollYProgress, [0, 1], [1, 0]) + const y = useTransform(scrollYProgress, [0, 1], [0, -100]) + + return ( + <> +
+
+ +
+
+
+ + {scrollYProgress.accelerate ? "true" : "false"} + + + ) +} + +const spacer = { height: "100vh" } +const targetStyle: React.CSSProperties = { + height: "100vh", + display: "flex", + alignItems: "center", + justifyContent: "center", +} +const box: React.CSSProperties = { + width: 100, + height: 100, + background: "red", +} diff --git a/packages/framer-motion/cypress/integration/scroll-target-transform.ts b/packages/framer-motion/cypress/integration/scroll-target-transform.ts new file mode 100644 index 0000000000..915b5b35ed --- /dev/null +++ b/packages/framer-motion/cypress/integration/scroll-target-transform.ts @@ -0,0 +1,31 @@ +describe("useScroll with target does not set accelerate", () => { + it("Does not set accelerate when target is provided", () => { + cy.visit("?test=scroll-target-transform") + .wait(200) + .get("#has-accelerate") + .should(([$el]: any) => { + expect($el.innerText).to.equal("false") + }) + }) + + it("Opacity updates via useTransform when scrolling", () => { + cy.visit("?test=scroll-target-transform") + .wait(200) + .get("#target") + .should(([$el]: any) => { + // Before scrolling, opacity should be near initial value + const initialOpacity = parseFloat( + getComputedStyle($el).opacity + ) + expect(initialOpacity).to.be.greaterThan(0) + }) + cy.scrollTo("bottom", { duration: 300 }) + .wait(200) + .get("#target") + .should(([$el]: any) => { + // After scrolling, opacity should have changed + const opacity = parseFloat(getComputedStyle($el).opacity) + expect(opacity).to.be.lessThan(1) + }) + }) +}) diff --git a/packages/framer-motion/src/render/dom/scroll/utils/can-use-native-timeline.ts b/packages/framer-motion/src/render/dom/scroll/utils/can-use-native-timeline.ts new file mode 100644 index 0000000000..9e3c04c1d2 --- /dev/null +++ b/packages/framer-motion/src/render/dom/scroll/utils/can-use-native-timeline.ts @@ -0,0 +1,7 @@ +import { supportsScrollTimeline } from "motion-dom" + +export function canUseNativeTimeline(target?: Element) { + return ( + typeof window !== "undefined" && !target && supportsScrollTimeline() + ) +} diff --git a/packages/framer-motion/src/render/dom/scroll/utils/get-timeline.ts b/packages/framer-motion/src/render/dom/scroll/utils/get-timeline.ts index 48eb5787d3..97a299c42e 100644 --- a/packages/framer-motion/src/render/dom/scroll/utils/get-timeline.ts +++ b/packages/framer-motion/src/render/dom/scroll/utils/get-timeline.ts @@ -1,6 +1,7 @@ -import { ProgressTimeline, supportsScrollTimeline } from "motion-dom" +import { ProgressTimeline } from "motion-dom" import { scrollInfo } from "../track" import { ScrollOptionsWithDefaults } from "../types" +import { canUseNativeTimeline } from "./can-use-native-timeline" declare global { interface Window { @@ -50,7 +51,7 @@ export function getTimeline({ if (!targetCache[axisKey]) { targetCache[axisKey] = - !options.target && supportsScrollTimeline() + canUseNativeTimeline(options.target) ? new ScrollTimeline({ source: container, axis } as any) : scrollTimelineFallback({ container, ...options }) } diff --git a/packages/framer-motion/src/value/use-scroll.ts b/packages/framer-motion/src/value/use-scroll.ts index 0efd38254f..c413275073 100644 --- a/packages/framer-motion/src/value/use-scroll.ts +++ b/packages/framer-motion/src/value/use-scroll.ts @@ -5,6 +5,7 @@ import { invariant } from "motion-utils" import { RefObject, useCallback, useEffect, useRef } from "react" import { scroll } from "../render/dom/scroll" import { ScrollInfoOptions } from "../render/dom/scroll/types" +import { canUseNativeTimeline } from "../render/dom/scroll/utils/can-use-native-timeline" import { useConstant } from "../utils/use-constant" import { useIsomorphicLayoutEffect } from "../utils/use-isomorphic-effect" @@ -26,6 +27,21 @@ const isRefPending = (ref?: RefObject) => { return !ref.current } +function makeAccelerateConfig( + axis: "x" | "y", + options: Omit, + container?: Element +) { + return { + factory: (animation: AnimationPlaybackControls) => + scroll(animation, { ...options, axis, container }), + times: [0, 1], + keyframes: [0, 1], + ease: (v: number) => v, + duration: 1, + } +} + export function useScroll({ container, target, @@ -33,31 +49,18 @@ export function useScroll({ }: UseScrollOptions = {}) { const values = useConstant(createScrollMotionValues) - values.scrollXProgress.accelerate = { - factory: (animation: AnimationPlaybackControls) => - scroll(animation, { - ...options, - axis: "x", - container: container?.current || undefined, - target: target?.current || undefined, - }), - times: [0, 1], - keyframes: [0, 1], - ease: (v: number) => v, - duration: 1, - } - values.scrollYProgress.accelerate = { - factory: (animation: AnimationPlaybackControls) => - scroll(animation, { - ...options, - axis: "y", - container: container?.current || undefined, - target: target?.current || undefined, - }), - times: [0, 1], - keyframes: [0, 1], - ease: (v: number) => v, - duration: 1, + if (canUseNativeTimeline(target?.current || undefined)) { + const resolvedContainer = container?.current || undefined + values.scrollXProgress.accelerate = makeAccelerateConfig( + "x", + options, + resolvedContainer + ) + values.scrollYProgress.accelerate = makeAccelerateConfig( + "y", + options, + resolvedContainer + ) } const scrollAnimation = useRef(null) From dbb66c982480823d672701db24ab82c6a2184c3f Mon Sep 17 00:00:00 2001 From: Matt Perry Date: Fri, 13 Feb 2026 10:53:44 +0100 Subject: [PATCH 2/8] Fix scroll-accelerate tests for environments without ScrollTimeline Make supportsScrollTimeline testable via supportsFlags, fix target ref guard in useScroll, make Cypress assertions conditional on ScrollTimeline support, and add Jest tests for accelerate propagation. Co-Authored-By: Claude Opus 4.6 --- .../cypress/integration/scroll-accelerate.ts | 10 ++- .../src/value/__tests__/use-scroll.test.tsx | 88 +++++++++++++++++++ .../framer-motion/src/value/use-scroll.ts | 2 +- .../src/utils/supports/scroll-timeline.ts | 7 +- 4 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 packages/framer-motion/src/value/__tests__/use-scroll.test.tsx diff --git a/packages/framer-motion/cypress/integration/scroll-accelerate.ts b/packages/framer-motion/cypress/integration/scroll-accelerate.ts index 8027b4922f..5efaff464f 100644 --- a/packages/framer-motion/cypress/integration/scroll-accelerate.ts +++ b/packages/framer-motion/cypress/integration/scroll-accelerate.ts @@ -4,7 +4,10 @@ describe("scroll timeline WAAPI acceleration", () => { .wait(200) .get("#direct-accelerated") .should(([$el]: any) => { - expect($el.innerText).to.equal("true") + const expected = (window as any).ScrollTimeline + ? "true" + : "false" + expect($el.innerText).to.equal(expected) }) }) @@ -16,7 +19,10 @@ describe("scroll timeline WAAPI acceleration", () => { // backgroundColor gets accelerate config propagated, // but VisualElement skips WAAPI creation since it's // not in the acceleratedValues set - expect($el.innerText).to.equal("true") + const expected = (window as any).ScrollTimeline + ? "true" + : "false" + expect($el.innerText).to.equal(expected) }) }) diff --git a/packages/framer-motion/src/value/__tests__/use-scroll.test.tsx b/packages/framer-motion/src/value/__tests__/use-scroll.test.tsx new file mode 100644 index 0000000000..24c1fbec89 --- /dev/null +++ b/packages/framer-motion/src/value/__tests__/use-scroll.test.tsx @@ -0,0 +1,88 @@ +import { supportsFlags } from "motion-dom" +import { useRef } from "react" +import { render } from "../../jest.setup" +import { useScroll } from "../use-scroll" +import { useTransform } from "../use-transform" + +describe("useScroll accelerate", () => { + afterEach(() => { + supportsFlags.scrollTimeline = undefined + }) + + test("sets accelerate on progress values when ScrollTimeline is supported and no target", () => { + supportsFlags.scrollTimeline = true + + let accelerateX: any + let accelerateY: any + + const Component = () => { + const { scrollXProgress, scrollYProgress } = useScroll() + accelerateX = scrollXProgress.accelerate + accelerateY = scrollYProgress.accelerate + return null + } + + render() + + expect(accelerateX).toBeDefined() + expect(accelerateY).toBeDefined() + }) + + test("does not set accelerate when target ref is provided", () => { + supportsFlags.scrollTimeline = true + + let accelerateX: any + let accelerateY: any + + const Component = () => { + const target = useRef(null) + const { scrollXProgress, scrollYProgress } = useScroll({ + target, + }) + accelerateX = scrollXProgress.accelerate + accelerateY = scrollYProgress.accelerate + return
+ } + + render() + + expect(accelerateX).toBeUndefined() + expect(accelerateY).toBeUndefined() + }) + + test("does not set accelerate when ScrollTimeline is not supported", () => { + supportsFlags.scrollTimeline = false + + let accelerateX: any + let accelerateY: any + + const Component = () => { + const { scrollXProgress, scrollYProgress } = useScroll() + accelerateX = scrollXProgress.accelerate + accelerateY = scrollYProgress.accelerate + return null + } + + render() + + expect(accelerateX).toBeUndefined() + expect(accelerateY).toBeUndefined() + }) + + test("propagates accelerate through useTransform", () => { + supportsFlags.scrollTimeline = true + + let transformAccelerate: any + + const Component = () => { + const { scrollYProgress } = useScroll() + const opacity = useTransform(scrollYProgress, [0, 1], [0, 1]) + transformAccelerate = opacity.accelerate + return null + } + + render() + + expect(transformAccelerate).toBeDefined() + }) +}) diff --git a/packages/framer-motion/src/value/use-scroll.ts b/packages/framer-motion/src/value/use-scroll.ts index c413275073..f596096bc8 100644 --- a/packages/framer-motion/src/value/use-scroll.ts +++ b/packages/framer-motion/src/value/use-scroll.ts @@ -49,7 +49,7 @@ export function useScroll({ }: UseScrollOptions = {}) { const values = useConstant(createScrollMotionValues) - if (canUseNativeTimeline(target?.current || undefined)) { + if (!target && canUseNativeTimeline()) { const resolvedContainer = container?.current || undefined values.scrollXProgress.accelerate = makeAccelerateConfig( "x", diff --git a/packages/motion-dom/src/utils/supports/scroll-timeline.ts b/packages/motion-dom/src/utils/supports/scroll-timeline.ts index ddf88b31e1..a949da5c4f 100644 --- a/packages/motion-dom/src/utils/supports/scroll-timeline.ts +++ b/packages/motion-dom/src/utils/supports/scroll-timeline.ts @@ -1,5 +1,5 @@ -import { memo } from "motion-utils" import { ProgressTimeline } from "../.." +import { memoSupports } from "./memo" declare global { interface Window { @@ -15,6 +15,7 @@ declare class ScrollTimeline implements ProgressTimeline { cancel?: VoidFunction } -export const supportsScrollTimeline = /* @__PURE__ */ memo( - () => window.ScrollTimeline !== undefined +export const supportsScrollTimeline = /* @__PURE__ */ memoSupports( + () => window.ScrollTimeline !== undefined, + "scrollTimeline" ) From 63cd3799151d36ccc97a985c9836b44bfbdafaf7 Mon Sep 17 00:00:00 2001 From: Matt Perry Date: Mon, 16 Feb 2026 15:35:24 +0100 Subject: [PATCH 3/8] Fix mask gradient "none" producing invisible element mask: none means "no masking" (fully visible), but getAnimatableNone was making all colors transparent (alpha=0), causing elements to start or end invisible when animating mask to/from "none". Added a mask value type that keeps colors opaque (alpha=1) when generating the zero-equivalent template. Co-Authored-By: Claude Opus 4.6 --- .../src/value/types/__tests__/index.test.ts | 33 +++++++++++++++++++ .../src/value/types/complex/mask.ts | 20 +++++++++++ .../src/value/types/maps/defaults.ts | 3 ++ .../src/value/types/utils/animatable-none.ts | 4 ++- 4 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 packages/motion-dom/src/value/types/complex/mask.ts diff --git a/packages/motion-dom/src/value/types/__tests__/index.test.ts b/packages/motion-dom/src/value/types/__tests__/index.test.ts index 50e03a83bd..c28712245a 100644 --- a/packages/motion-dom/src/value/types/__tests__/index.test.ts +++ b/packages/motion-dom/src/value/types/__tests__/index.test.ts @@ -4,6 +4,7 @@ import { hsla } from "../color/hsla" import { rgba, rgbUnit } from "../color/rgba" import { complex } from "../complex" import { filter } from "../complex/filter" +import { mask } from "../complex/mask" import { alpha } from "../numbers" import { degrees, percent, progressPercentage, px } from "../numbers/units" import { colorRegex } from "../utils/color-regex" @@ -575,3 +576,35 @@ describe("filter", () => { ) }) }) + +describe("mask", () => { + it("should create an animatableNone with opaque colors", () => { + expect( + mask.getAnimatableNone( + "linear-gradient(0deg, rgba(0,0,0,0) 0%, rgb(0,0,0) 100%)" + ) + ).toBe( + "linear-gradient(0deg, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 1) 0%)" + ) + }) + + it("should zero numbers but keep colors opaque with hex", () => { + expect( + mask.getAnimatableNone( + "linear-gradient(180deg, #000000 0%, #000000 50%, rgba(0,0,0,0) 100%)" + ) + ).toBe( + "linear-gradient(0deg, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 1) 0%)" + ) + }) + + it("should handle radial gradients", () => { + expect( + mask.getAnimatableNone( + "radial-gradient(circle at 50% 25%, rgba(0,0,0,1), rgba(0,0,0,0))" + ) + ).toBe( + "radial-gradient(circle at 0% 0%, rgba(0, 0, 0, 1), rgba(0, 0, 0, 1))" + ) + }) +}) diff --git a/packages/motion-dom/src/value/types/complex/mask.ts b/packages/motion-dom/src/value/types/complex/mask.ts new file mode 100644 index 0000000000..77f0784577 --- /dev/null +++ b/packages/motion-dom/src/value/types/complex/mask.ts @@ -0,0 +1,20 @@ +import { complex } from "." +import { AnyResolvedKeyframe } from "../../../animation/types" +import { Color } from "../types" + +const makeOpaque = (v: number | Color | string) => { + if (typeof v === "number") return 0 + if (typeof v === "object" && "alpha" in v) return { ...v, alpha: 1 } + return v +} + +function getAnimatableNone(v: AnyResolvedKeyframe) { + const parsed = complex.parse(v) + const transformer = complex.createTransformer(v) + return transformer(parsed.map(makeOpaque)) +} + +export const mask = { + ...complex, + getAnimatableNone, +} diff --git a/packages/motion-dom/src/value/types/maps/defaults.ts b/packages/motion-dom/src/value/types/maps/defaults.ts index 3692694980..fd50cb763b 100644 --- a/packages/motion-dom/src/value/types/maps/defaults.ts +++ b/packages/motion-dom/src/value/types/maps/defaults.ts @@ -1,5 +1,6 @@ import { color } from "../color" import { filter } from "../complex/filter" +import { mask } from "../complex/mask" import { numberValueTypes } from "./number" import { ValueTypeMap } from "./types" @@ -24,6 +25,8 @@ export const defaultValueTypes: ValueTypeMap = { borderLeftColor: color, filter, WebkitFilter: filter, + mask, + WebkitMask: mask, } /** diff --git a/packages/motion-dom/src/value/types/utils/animatable-none.ts b/packages/motion-dom/src/value/types/utils/animatable-none.ts index 16443ff670..160f338920 100644 --- a/packages/motion-dom/src/value/types/utils/animatable-none.ts +++ b/packages/motion-dom/src/value/types/utils/animatable-none.ts @@ -1,10 +1,12 @@ import { complex } from "../complex" import { filter } from "../complex/filter" +import { mask } from "../complex/mask" import { getDefaultValueType } from "../maps/defaults" export function getAnimatableNone(key: string, value: string) { let defaultValueType = getDefaultValueType(key) - if (defaultValueType !== filter) defaultValueType = complex + if (defaultValueType !== filter && defaultValueType !== mask) + defaultValueType = complex // If value is not recognised as animatable, ie "none", create an animatable version origin based on the target return defaultValueType.getAnimatableNone ? defaultValueType.getAnimatableNone(value) From c39c64e79e0955c38b1078005924ac13d56ebfdb Mon Sep 17 00:00:00 2001 From: Matt Perry Date: Mon, 16 Feb 2026 15:37:53 +0100 Subject: [PATCH 4/8] Use a Set for custom animatable-none types Co-Authored-By: Claude Opus 4.6 --- packages/motion-dom/src/value/types/utils/animatable-none.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/motion-dom/src/value/types/utils/animatable-none.ts b/packages/motion-dom/src/value/types/utils/animatable-none.ts index 160f338920..c6974d0914 100644 --- a/packages/motion-dom/src/value/types/utils/animatable-none.ts +++ b/packages/motion-dom/src/value/types/utils/animatable-none.ts @@ -3,10 +3,11 @@ import { filter } from "../complex/filter" import { mask } from "../complex/mask" import { getDefaultValueType } from "../maps/defaults" +const customTypes = new Set([filter, mask]) + export function getAnimatableNone(key: string, value: string) { let defaultValueType = getDefaultValueType(key) - if (defaultValueType !== filter && defaultValueType !== mask) - defaultValueType = complex + if (!customTypes.has(defaultValueType)) defaultValueType = complex // If value is not recognised as animatable, ie "none", create an animatable version origin based on the target return defaultValueType.getAnimatableNone ? defaultValueType.getAnimatableNone(value) From 775c5a3b203e1fcbc97db321703aa435a785fa36 Mon Sep 17 00:00:00 2001 From: Matt Perry Date: Mon, 16 Feb 2026 15:38:48 +0100 Subject: [PATCH 5/8] Mark customTypes Set as pure for tree shaking Co-Authored-By: Claude Opus 4.6 --- packages/motion-dom/src/value/types/utils/animatable-none.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/motion-dom/src/value/types/utils/animatable-none.ts b/packages/motion-dom/src/value/types/utils/animatable-none.ts index c6974d0914..94d487ac9b 100644 --- a/packages/motion-dom/src/value/types/utils/animatable-none.ts +++ b/packages/motion-dom/src/value/types/utils/animatable-none.ts @@ -3,7 +3,7 @@ import { filter } from "../complex/filter" import { mask } from "../complex/mask" import { getDefaultValueType } from "../maps/defaults" -const customTypes = new Set([filter, mask]) +const customTypes = /*@__PURE__*/ new Set([filter, mask]) export function getAnimatableNone(key: string, value: string) { let defaultValueType = getDefaultValueType(key) From 8c513ba76de9b23d65f8cb981093ac933507e1da Mon Sep 17 00:00:00 2001 From: Matt Perry Date: Mon, 16 Feb 2026 16:33:45 +0100 Subject: [PATCH 6/8] Fix build error and inline mask value type Co-Authored-By: Claude Opus 4.6 --- .../src/value/types/complex/mask.ts | 23 ++++++++----------- .../src/value/types/utils/animatable-none.ts | 2 +- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/packages/motion-dom/src/value/types/complex/mask.ts b/packages/motion-dom/src/value/types/complex/mask.ts index 77f0784577..e03044044c 100644 --- a/packages/motion-dom/src/value/types/complex/mask.ts +++ b/packages/motion-dom/src/value/types/complex/mask.ts @@ -1,20 +1,15 @@ import { complex } from "." import { AnyResolvedKeyframe } from "../../../animation/types" -import { Color } from "../types" - -const makeOpaque = (v: number | Color | string) => { - if (typeof v === "number") return 0 - if (typeof v === "object" && "alpha" in v) return { ...v, alpha: 1 } - return v -} - -function getAnimatableNone(v: AnyResolvedKeyframe) { - const parsed = complex.parse(v) - const transformer = complex.createTransformer(v) - return transformer(parsed.map(makeOpaque)) -} export const mask = { ...complex, - getAnimatableNone, + getAnimatableNone: (v: AnyResolvedKeyframe) => { + const parsed = complex.parse(v) + const transformer = complex.createTransformer(v) + return transformer( + parsed.map((v) => + typeof v === "number" ? 0 : typeof v === "object" ? { ...v, alpha: 1 } : v + ) + ) + }, } diff --git a/packages/motion-dom/src/value/types/utils/animatable-none.ts b/packages/motion-dom/src/value/types/utils/animatable-none.ts index 94d487ac9b..0f768db3df 100644 --- a/packages/motion-dom/src/value/types/utils/animatable-none.ts +++ b/packages/motion-dom/src/value/types/utils/animatable-none.ts @@ -7,7 +7,7 @@ const customTypes = /*@__PURE__*/ new Set([filter, mask]) export function getAnimatableNone(key: string, value: string) { let defaultValueType = getDefaultValueType(key) - if (!customTypes.has(defaultValueType)) defaultValueType = complex + if (!customTypes.has(defaultValueType as any)) defaultValueType = complex // If value is not recognised as animatable, ie "none", create an animatable version origin based on the target return defaultValueType.getAnimatableNone ? defaultValueType.getAnimatableNone(value) From 9447ee2ff9b7aa08c1cb8b2438af9fbe454fba3b Mon Sep 17 00:00:00 2001 From: Matt Perry Date: Tue, 17 Feb 2026 11:04:57 +0100 Subject: [PATCH 7/8] Updating changelog --- CHANGELOG.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ed6ec09cd..acbc9eac71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,17 @@ Motion adheres to [Semantic Versioning](http://semver.org/). Undocumented APIs should be considered internal and may change without warning. -## [12.34.0] 2026-02-09 +## [12.34.1] 2026-02-17 ### Fixed +- `useScroll`: Ensure animations aren't hardware accelerated when `target` is set. +- Improve animatable `"none"` generation for mask values. + +## [12.34.0] 2026-02-09 + +### Added + - `useScroll`: Hardware accelerated animations. ## [12.33.2] 2026-02-06 From 57263d825460bca345ffcb959c5463fea40f074e Mon Sep 17 00:00:00 2001 From: Matt Perry Date: Tue, 17 Feb 2026 11:05:13 +0100 Subject: [PATCH 8/8] v12.34.1 --- dev/html/package.json | 8 ++++---- dev/next/package.json | 4 ++-- dev/react-19/package.json | 4 ++-- dev/react/package.json | 4 ++-- lerna.json | 2 +- packages/framer-motion/package.json | 4 ++-- packages/motion-dom/package.json | 2 +- packages/motion/package.json | 4 ++-- yarn.lock | 22 +++++++++++----------- 9 files changed, 27 insertions(+), 27 deletions(-) diff --git a/dev/html/package.json b/dev/html/package.json index 071a7a2e6a..0e4bd03c0d 100644 --- a/dev/html/package.json +++ b/dev/html/package.json @@ -1,7 +1,7 @@ { "name": "html-env", "private": true, - "version": "12.34.0", + "version": "12.34.1", "type": "module", "scripts": { "dev": "vite", @@ -10,9 +10,9 @@ "preview": "vite preview" }, "dependencies": { - "framer-motion": "^12.34.0", - "motion": "^12.34.0", - "motion-dom": "^12.34.0", + "framer-motion": "^12.34.1", + "motion": "^12.34.1", + "motion-dom": "^12.34.1", "react": "^18.3.1", "react-dom": "^18.3.1" }, diff --git a/dev/next/package.json b/dev/next/package.json index f5a4153628..b611c65b36 100644 --- a/dev/next/package.json +++ b/dev/next/package.json @@ -1,7 +1,7 @@ { "name": "next-env", "private": true, - "version": "12.34.0", + "version": "12.34.1", "type": "module", "scripts": { "dev": "next dev", @@ -10,7 +10,7 @@ "build": "next build" }, "dependencies": { - "motion": "^12.34.0", + "motion": "^12.34.1", "next": "15.5.10", "react": "19.0.0", "react-dom": "19.0.0" diff --git a/dev/react-19/package.json b/dev/react-19/package.json index 3ee71c3b14..2e0260ddc8 100644 --- a/dev/react-19/package.json +++ b/dev/react-19/package.json @@ -1,7 +1,7 @@ { "name": "react-19-env", "private": true, - "version": "12.34.0", + "version": "12.34.1", "type": "module", "scripts": { "dev": "vite", @@ -11,7 +11,7 @@ "preview": "vite preview" }, "dependencies": { - "motion": "^12.34.0", + "motion": "^12.34.1", "react": "^19.0.0", "react-dom": "^19.0.0" }, diff --git a/dev/react/package.json b/dev/react/package.json index b41af2a113..df5051792d 100644 --- a/dev/react/package.json +++ b/dev/react/package.json @@ -1,7 +1,7 @@ { "name": "react-env", "private": true, - "version": "12.34.0", + "version": "12.34.1", "type": "module", "scripts": { "dev": "yarn vite", @@ -11,7 +11,7 @@ "preview": "yarn vite preview" }, "dependencies": { - "framer-motion": "^12.34.0", + "framer-motion": "^12.34.1", "react": "^18.3.1", "react-dom": "^18.3.1" }, diff --git a/lerna.json b/lerna.json index 9d284f3977..147512e804 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "12.34.0", + "version": "12.34.1", "packages": [ "packages/*", "dev/*" diff --git a/packages/framer-motion/package.json b/packages/framer-motion/package.json index a0f9f15e29..f15fc7c908 100644 --- a/packages/framer-motion/package.json +++ b/packages/framer-motion/package.json @@ -1,6 +1,6 @@ { "name": "framer-motion", - "version": "12.34.0", + "version": "12.34.1", "description": "A simple and powerful JavaScript animation library", "main": "dist/cjs/index.js", "module": "dist/es/index.mjs", @@ -88,7 +88,7 @@ "measure": "rollup -c ./rollup.size.config.mjs" }, "dependencies": { - "motion-dom": "^12.34.0", + "motion-dom": "^12.34.1", "motion-utils": "^12.29.2", "tslib": "^2.4.0" }, diff --git a/packages/motion-dom/package.json b/packages/motion-dom/package.json index 42dd9491f9..406b4128ba 100644 --- a/packages/motion-dom/package.json +++ b/packages/motion-dom/package.json @@ -1,6 +1,6 @@ { "name": "motion-dom", - "version": "12.34.0", + "version": "12.34.1", "author": "Matt Perry", "license": "MIT", "repository": "https://github.com/motiondivision/motion", diff --git a/packages/motion/package.json b/packages/motion/package.json index e418097564..6b6185139c 100644 --- a/packages/motion/package.json +++ b/packages/motion/package.json @@ -1,6 +1,6 @@ { "name": "motion", - "version": "12.34.0", + "version": "12.34.1", "description": "An animation library for JavaScript and React.", "main": "dist/cjs/index.js", "module": "dist/es/index.mjs", @@ -76,7 +76,7 @@ "postpublish": "git push --tags" }, "dependencies": { - "framer-motion": "^12.34.0", + "framer-motion": "^12.34.1", "tslib": "^2.4.0" }, "peerDependencies": { diff --git a/yarn.lock b/yarn.lock index e9dd6863e7..77f2a9217f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7437,14 +7437,14 @@ __metadata: languageName: node linkType: hard -"framer-motion@^12.34.0, framer-motion@workspace:packages/framer-motion": +"framer-motion@^12.34.1, framer-motion@workspace:packages/framer-motion": version: 0.0.0-use.local resolution: "framer-motion@workspace:packages/framer-motion" dependencies: "@radix-ui/react-dialog": ^1.1.15 "@thednp/dommatrix": ^2.0.11 "@types/three": 0.137.0 - motion-dom: ^12.34.0 + motion-dom: ^12.34.1 motion-utils: ^12.29.2 three: 0.137.0 tslib: ^2.4.0 @@ -8209,9 +8209,9 @@ __metadata: version: 0.0.0-use.local resolution: "html-env@workspace:dev/html" dependencies: - framer-motion: ^12.34.0 - motion: ^12.34.0 - motion-dom: ^12.34.0 + framer-motion: ^12.34.1 + motion: ^12.34.1 + motion-dom: ^12.34.1 react: ^18.3.1 react-dom: ^18.3.1 vite: ^5.2.0 @@ -10953,7 +10953,7 @@ __metadata: languageName: node linkType: hard -"motion-dom@^12.34.0, motion-dom@workspace:packages/motion-dom": +"motion-dom@^12.34.1, motion-dom@workspace:packages/motion-dom": version: 0.0.0-use.local resolution: "motion-dom@workspace:packages/motion-dom" dependencies: @@ -11032,11 +11032,11 @@ __metadata: languageName: unknown linkType: soft -"motion@^12.34.0, motion@workspace:packages/motion": +"motion@^12.34.1, motion@workspace:packages/motion": version: 0.0.0-use.local resolution: "motion@workspace:packages/motion" dependencies: - framer-motion: ^12.34.0 + framer-motion: ^12.34.1 tslib: ^2.4.0 peerDependencies: "@emotion/is-prop-valid": "*" @@ -11153,7 +11153,7 @@ __metadata: version: 0.0.0-use.local resolution: "next-env@workspace:dev/next" dependencies: - motion: ^12.34.0 + motion: ^12.34.1 next: 15.5.10 react: 19.0.0 react-dom: 19.0.0 @@ -12625,7 +12625,7 @@ __metadata: "@typescript-eslint/parser": ^7.2.0 "@vitejs/plugin-react-swc": ^3.5.0 eslint-plugin-react-refresh: ^0.4.6 - motion: ^12.34.0 + motion: ^12.34.1 react: ^19.0.0 react-dom: ^19.0.0 vite: ^5.2.0 @@ -12709,7 +12709,7 @@ __metadata: "@typescript-eslint/parser": ^7.2.0 "@vitejs/plugin-react-swc": ^3.5.0 eslint-plugin-react-refresh: ^0.4.6 - framer-motion: ^12.34.0 + framer-motion: ^12.34.1 react: ^18.3.1 react-dom: ^18.3.1 vite: ^5.2.0